// 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:math' as math;
import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
import 'feedback_tester.dart';

class TestIcon extends StatefulWidget {
  const TestIcon({ super.key });

  @override
  TestIconState createState() => TestIconState();
}

class TestIconState extends State<TestIcon> {
  late IconThemeData iconTheme;

  @override
  Widget build(BuildContext context) {
    iconTheme = IconTheme.of(context);
    return const Icon(Icons.add);
  }
}

class TestText extends StatefulWidget {
  const TestText(this.text, { super.key });

  final String text;

  @override
  TestTextState createState() => TestTextState();
}

class TestTextState extends State<TestText> {
  late TextStyle textStyle;

  @override
  Widget build(BuildContext context) {
    textStyle = DefaultTextStyle.of(context).style;
    return Text(widget.text);
  }
}

void main() {
  testWidgets('ListTile geometry (LTR)', (WidgetTester tester) async {
    // See https://material.io/go/design-lists

    final Key leadingKey = GlobalKey();
    final Key trailingKey = GlobalKey();
    late bool hasSubtitle;

    const double leftPadding = 10.0;
    const double rightPadding = 20.0;
    Widget buildFrame({ bool dense = false, bool isTwoLine = false, bool isThreeLine = false, double textScaleFactor = 1.0, double? subtitleScaleFactor }) {
      hasSubtitle = isTwoLine || isThreeLine;
      subtitleScaleFactor ??= textScaleFactor;
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: MediaQuery(
          data: MediaQueryData(
            padding: const EdgeInsets.only(left: leftPadding, right: rightPadding),
            textScaleFactor: textScaleFactor,
          ),
          child: Material(
            child: Center(
              child: ListTile(
                leading: SizedBox(key: leadingKey, width: 24.0, height: 24.0),
                title: const Text('title'),
                subtitle: hasSubtitle ? Text('subtitle', textScaleFactor: subtitleScaleFactor) : null,
                trailing: SizedBox(key: trailingKey, width: 24.0, height: 24.0),
                dense: dense,
                isThreeLine: isThreeLine,
              ),
            ),
          ),
        ),
      );
    }

    void testChildren() {
      expect(find.byKey(leadingKey), findsOneWidget);
      expect(find.text('title'), findsOneWidget);
      if (hasSubtitle) {
        expect(find.text('subtitle'), findsOneWidget);
      }
      expect(find.byKey(trailingKey), findsOneWidget);
    }

    double left(String text) => tester.getTopLeft(find.text(text)).dx;
    double top(String text) => tester.getTopLeft(find.text(text)).dy;
    double bottom(String text) => tester.getBottomLeft(find.text(text)).dy;
    double height(String text) => tester.getRect(find.text(text)).height;

    double leftKey(Key key) => tester.getTopLeft(find.byKey(key)).dx;
    double rightKey(Key key) => tester.getTopRight(find.byKey(key)).dx;
    double widthKey(Key key) => tester.getSize(find.byKey(key)).width;
    double heightKey(Key key) => tester.getSize(find.byKey(key)).height;

    // ListTiles are contained by a SafeArea defined like this:
    // SafeArea(top: false, bottom: false, minimum: contentPadding)
    // The default contentPadding is 16.0 on the left and 24.0 on the right.
    void testHorizontalGeometry() {
      expect(leftKey(leadingKey), math.max(16.0, leftPadding));
      expect(left('title'), 40.0 + math.max(16.0, leftPadding));
      if (hasSubtitle) {
        expect(left('subtitle'), 40.0 + math.max(16.0, leftPadding));
      }
      expect(left('title'), rightKey(leadingKey) + 16.0);
      expect(rightKey(trailingKey), 800.0 - math.max(24.0, rightPadding));
      expect(widthKey(trailingKey), 24.0);
    }

    void testVerticalGeometry(double expectedHeight) {
      final Rect tileRect = tester.getRect(find.byType(ListTile));
      expect(tileRect.size, Size(800.0, expectedHeight));
      expect(top('title'), greaterThanOrEqualTo(tileRect.top));
      if (hasSubtitle) {
        expect(top('subtitle'), greaterThanOrEqualTo(bottom('title')));
        expect(bottom('subtitle'), lessThan(tileRect.bottom));
      } else {
        expect(top('title'), equals(tileRect.top + (tileRect.height - height('title')) / 2.0));
      }
      expect(heightKey(trailingKey), 24.0);
    }

    await tester.pumpWidget(buildFrame());
    testChildren();
    testHorizontalGeometry();
    testVerticalGeometry(56.0);

    await tester.pumpWidget(buildFrame(isTwoLine: true));
    testChildren();
    testHorizontalGeometry();
    testVerticalGeometry(72.0);

    await tester.pumpWidget(buildFrame(isThreeLine: true));
    testChildren();
    testHorizontalGeometry();
    testVerticalGeometry(88.0);

    await tester.pumpWidget(buildFrame(textScaleFactor: 4.0));
    testChildren();
    testHorizontalGeometry();
    testVerticalGeometry(112.0);

    await tester.pumpWidget(buildFrame(isTwoLine: true, textScaleFactor: 4.0));
    testChildren();
    testHorizontalGeometry();
    // TODO(tahatesser): https://github.com/flutter/flutter/issues/99933
    //                A bug in the HTML renderer and/or Chrome 96+ causes a
    //                discrepancy in the paragraph height.
    const bool hasIssue99933 = kIsWeb && !bool.fromEnvironment('FLUTTER_WEB_USE_SKIA');
    testVerticalGeometry(hasIssue99933 ? 193 : 192.0);

    // Make sure that the height of a large subtitle is taken into account.
    await tester.pumpWidget(buildFrame(isTwoLine: true, textScaleFactor: 0.5, subtitleScaleFactor: 4.0));
    testChildren();
    testHorizontalGeometry();
    testVerticalGeometry(hasIssue99933 ? 109 : 108.0);

    await tester.pumpWidget(buildFrame(isThreeLine: true, textScaleFactor: 4.0));
    testChildren();
    testHorizontalGeometry();
    testVerticalGeometry(hasIssue99933 ? 193 : 192.0);
  });

  testWidgets('ListTile geometry (RTL)', (WidgetTester tester) async {
    const double leftPadding = 10.0;
    const double rightPadding = 20.0;
    await tester.pumpWidget(MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const MediaQuery(
        data: MediaQueryData(
          padding: EdgeInsets.only(left: leftPadding, right: rightPadding),
        ),
        child: Directionality(
          textDirection: TextDirection.rtl,
          child: Material(
            child: Center(
              child: ListTile(
                leading: Text('L'),
                title: Text('title'),
                trailing: Text('T'),
              ),
            ),
          ),
        ),
      ),
    ));

    double left(String text) => tester.getTopLeft(find.text(text)).dx;
    double right(String text) => tester.getTopRight(find.text(text)).dx;

    void testHorizontalGeometry() {
      expect(right('L'), 800.0 - math.max(16.0, rightPadding));
      expect(right('title'), 800.0 - 40.0 - math.max(16.0, rightPadding));
      expect(left('T'), math.max(24.0, leftPadding));
    }

    testHorizontalGeometry();
  });

  testWidgets('ListTile.divideTiles', (WidgetTester tester) async {
    final List<String> titles = <String>[ 'first', 'second', 'third' ];

    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: Builder(
          builder: (BuildContext context) {
            return ListView(
              children: ListTile.divideTiles(
                context: context,
                tiles: titles.map<Widget>((String title) => ListTile(title: Text(title))),
              ).toList(),
            );
          },
        ),
      ),
    ));

    expect(find.text('first'), findsOneWidget);
    expect(find.text('second'), findsOneWidget);
    expect(find.text('third'), findsOneWidget);
  });

  testWidgets('ListTile.divideTiles with empty list', (WidgetTester tester) async {
    final Iterable<Widget> output = ListTile.divideTiles(tiles: <Widget>[], color: Colors.grey);
    expect(output, isEmpty);
  });

  testWidgets('ListTile.divideTiles with single item list', (WidgetTester tester) async {
    final Iterable<Widget> output = ListTile.divideTiles(tiles: const <Widget>[SizedBox()], color: Colors.grey);
    expect(output.single, isA<SizedBox>());
  });

  testWidgets('ListTile.divideTiles only runs the generator once', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/pull/78879
    int callCount = 0;
    Iterable<Widget> generator() sync* {
      callCount += 1;
      yield const Text('');
      yield const Text('');
    }

    final List<Widget> output = ListTile.divideTiles(tiles: generator(), color: Colors.grey).toList();
    expect(output, hasLength(2));
    expect(callCount, 1);
  });

  testWidgets('ListTile semantics', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);

    await tester.pumpWidget(
      Material(
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: MediaQuery(
            data: const MediaQueryData(),
            child: Column(
              children: <Widget>[
                const ListTile(
                  title: Text('one'),
                ),
                ListTile(
                  title: const Text('two'),
                  onTap: () {},
                ),
                const ListTile(
                  title: Text('three'),
                  selected: true,
                ),
                const ListTile(
                  title: Text('four'),
                  enabled: false,
                ),
              ],
            ),
          ),
        ),
      ),
    );

    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics.rootChild(
              flags: <SemanticsFlag>[
                SemanticsFlag.hasEnabledState,
                SemanticsFlag.isEnabled,
              ],
              label: 'one',
            ),
            TestSemantics.rootChild(
              flags: <SemanticsFlag>[
                SemanticsFlag.hasEnabledState,
                SemanticsFlag.isEnabled,
                SemanticsFlag.isFocusable,
              ],
              actions: <SemanticsAction>[SemanticsAction.tap],
              label: 'two',
            ),
            TestSemantics.rootChild(
              flags: <SemanticsFlag>[
                SemanticsFlag.isSelected,
                SemanticsFlag.hasEnabledState,
                SemanticsFlag.isEnabled,
              ],
              label: 'three',
            ),
            TestSemantics.rootChild(
              flags: <SemanticsFlag>[
                SemanticsFlag.hasEnabledState,
              ],
              label: 'four',
            ),
          ],
        ),
        ignoreTransform: true,
        ignoreId: true,
        ignoreRect: true,
      ),
    );

    semantics.dispose();
  });

  testWidgets('ListTile contentPadding', (WidgetTester tester) async {
    Widget buildFrame(TextDirection textDirection) {
      return MediaQuery(
        data: const MediaQueryData(),
        child: Directionality(
          textDirection: textDirection,
          child: Material(
            child: Container(
              alignment: Alignment.topLeft,
              child: const ListTile(
                contentPadding: EdgeInsetsDirectional.only(
                  start: 10.0,
                  end: 20.0,
                  top: 30.0,
                  bottom: 40.0,
                ),
                leading: Text('L'),
                title: Text('title'),
                trailing: Text('T'),
              ),
            ),
          ),
        ),
      );
    }

    double left(String text) => tester.getTopLeft(find.text(text)).dx;
    double right(String text) => tester.getTopRight(find.text(text)).dx;

    await tester.pumpWidget(buildFrame(TextDirection.ltr));

    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 126.0)); // 126 = 56 + 30 + 40
    expect(left('L'), 10.0); // contentPadding.start = 10
    expect(right('T'), 780.0); // 800 - contentPadding.end

    await tester.pumpWidget(buildFrame(TextDirection.rtl));

    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 126.0)); // 126 = 56 + 30 + 40
    expect(left('T'), 20.0); // contentPadding.end = 20
    expect(right('L'), 790.0); // 800 - contentPadding.start
  });

  testWidgets('ListTile wide leading Widget', (WidgetTester tester) async {
    const Key leadingKey = ValueKey<String>('L');

    Widget buildFrame(double leadingWidth, TextDirection textDirection) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Directionality(
          textDirection: textDirection,
          child: Material(
            child: Container(
              alignment: Alignment.topLeft,
              child: ListTile(
                contentPadding: EdgeInsets.zero,
                leading: SizedBox(key: leadingKey, width: leadingWidth, height: 32.0),
                title: const Text('title'),
                subtitle: const Text('subtitle'),
              ),
            ),
          ),
        ),
      );
    }

    double left(String text) => tester.getTopLeft(find.text(text)).dx;
    double right(String text) => tester.getTopRight(find.text(text)).dx;

    // textDirection = LTR

    // Two-line tile's height = 72, leading 24x32 widget is positioned in the center.
    await tester.pumpWidget(buildFrame(24.0, TextDirection.ltr));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
    expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(0.0, 20.0));
    expect(tester.getBottomRight(find.byKey(leadingKey)), const Offset(24.0, 20.0 + 32.0));

    // Leading widget's width is 20, so default layout: the left edges of the
    // title and subtitle are at 40dps, leading widget width is 24dp and 16dp
    // is horizontalTitleGap (contentPadding is zero).
    expect(left('title'), 40.0);
    expect(left('subtitle'), 40.0);

    // If the leading widget is wider than 40 it is separated from the
    // title and subtitle by 16.
    await tester.pumpWidget(buildFrame(56.0, TextDirection.ltr));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
    expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(0.0, 20.0));
    expect(tester.getBottomRight(find.byKey(leadingKey)), const Offset(56.0, 20.0 + 32.0));
    expect(left('title'), 72.0);
    expect(left('subtitle'), 72.0);

    // Same tests, textDirection = RTL

    await tester.pumpWidget(buildFrame(24.0, TextDirection.rtl));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
    expect(tester.getTopRight(find.byKey(leadingKey)), const Offset(800.0, 20.0));
    expect(tester.getBottomLeft(find.byKey(leadingKey)), const Offset(800.0 - 24.0, 20.0 + 32.0));
    expect(right('title'), 800.0 - 40.0);
    expect(right('subtitle'), 800.0 - 40.0);

    await tester.pumpWidget(buildFrame(56.0, TextDirection.rtl));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
    expect(tester.getTopRight(find.byKey(leadingKey)), const Offset(800.0, 20.0));
    expect(tester.getBottomLeft(find.byKey(leadingKey)), const Offset(800.0 - 56.0, 20.0 + 32.0));
    expect(right('title'), 800.0 - 72.0);
    expect(right('subtitle'), 800.0 - 72.0);
  });

  testWidgets('ListTile leading and trailing positions', (WidgetTester tester) async {
    // This test is based on the redlines at
    // https://material.io/design/components/lists.html#specs

    // "ONE"-LINE
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: ListView(
            children: const <Widget>[
              ListTile(
                leading: CircleAvatar(),
                trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                title: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
              ),
              ListTile(
                leading: CircleAvatar(),
                trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                title: Text('A'),
              ),
            ],
          ),
        ),
      ),
    );
    await tester.pump(const Duration(seconds: 2)); // the text styles are animated when we change dense
    //                                                                          LEFT                 TOP                   WIDTH  HEIGHT

    expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,           0.0, 800.0, 328.0));
    expect(tester.getRect(find.byType(CircleAvatar).at(0)), const Rect.fromLTWH(               16.0,         144.0,  40.0,  40.0));
    expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(800.0 - 24.0 - 24.0,         152.0,  24.0,  24.0));
    expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0,  328.0       , 800.0,  56.0));
    expect(tester.getRect(find.byType(CircleAvatar).at(1)), const Rect.fromLTWH(               16.0,  328.0 +  8.0,  40.0,  40.0));
    expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 24.0,  328.0 + 16.0,  24.0,  24.0));

    // "TWO"-LINE
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: ListView(
            children: const <Widget>[
              ListTile(
                leading: CircleAvatar(),
                trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                title: Text('A'),
                subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
              ),
              ListTile(
                leading: CircleAvatar(),
                trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                title: Text('A'),
                subtitle: Text('A'),
              ),
            ],
          ),
        ),
      ),
    );
    // TODO(tahatesser): https://github.com/flutter/flutter/issues/99933
    //                A bug in the HTML renderer and/or Chrome 96+ causes a
    //                discrepancy in the paragraph height.
    const bool hasIssue99933 = kIsWeb && !bool.fromEnvironment('FLUTTER_WEB_USE_SKIA');
    const double height = hasIssue99933 ? 301.0 : 300;
    const double avatarTop = hasIssue99933 ? 130.5 : 130.0;
    const double placeholderTop = hasIssue99933 ? 138.5 : 138.0;
    //                                                                          LEFT                 TOP          WIDTH  HEIGHT
    expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,            0.0, 800.0, height));
    expect(tester.getRect(find.byType(CircleAvatar).at(0)), const Rect.fromLTWH(               16.0,      avatarTop,  40.0,  40.0));
    expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(800.0 - 24.0 - 24.0, placeholderTop,  24.0,  24.0));
    expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0,  height       , 800.0,  72.0));
    expect(tester.getRect(find.byType(CircleAvatar).at(1)), const Rect.fromLTWH(               16.0,  height + 16.0,  40.0,  40.0));
    expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 24.0,  height + 24.0,  24.0,  24.0));

    // THREE-LINE
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: ListView(
            children: const <Widget>[
              ListTile(
                isThreeLine: true,
                leading: CircleAvatar(),
                trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                title: Text('A'),
                subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
              ),
              ListTile(
                isThreeLine: true,
                leading: CircleAvatar(),
                trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                title: Text('A'),
                subtitle: Text('A'),
              ),
            ],
          ),
        ),
      ),
    );
    //                                                                          LEFT                 TOP          WIDTH  HEIGHT
    expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,          0.0, 800.0, height));
    expect(tester.getRect(find.byType(CircleAvatar).at(0)), const Rect.fromLTWH(               16.0,          8.0,  40.0,  40.0));
    expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(800.0 - 24.0 - 24.0,          8.0,  24.0,  24.0));
    expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0, height      , 800.0,  88.0));
    expect(tester.getRect(find.byType(CircleAvatar).at(1)), const Rect.fromLTWH(               16.0, height + 8.0,  40.0,  40.0));
    expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 8.0,  24.0,  24.0));

    // "ONE-LINE" with Small Leading Widget
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: ListView(
            children: const <Widget>[
              ListTile(
                leading: SizedBox(height: 12.0, width: 24.0, child: Placeholder()),
                trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                title: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
              ),
              ListTile(
                leading: SizedBox(height: 12.0, width: 24.0, child: Placeholder()),
                trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                title: Text('A'),
              ),
            ],
          ),
        ),
      ),
    );
    await tester.pump(const Duration(seconds: 2)); // the text styles are animated when we change dense
    //                                                                          LEFT                 TOP           WIDTH  HEIGHT
    expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,          0.0, 800.0, 328.0));
    expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(               16.0,        158.0,  24.0,  12.0));
    expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 24.0,        152.0,  24.0,  24.0));
    expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0, 328.0       , 800.0,  56.0));
    expect(tester.getRect(find.byType(Placeholder).at(2)),  const Rect.fromLTWH(               16.0, 328.0 + 22.0,  24.0,  12.0));
    expect(tester.getRect(find.byType(Placeholder).at(3)),  const Rect.fromLTWH(800.0 - 24.0 - 24.0, 328.0 + 16.0,  24.0,  24.0));
  });

  testWidgets('ListTile leading icon height does not exceed ListTile height', (WidgetTester tester) async {
    // regression test for https://github.com/flutter/flutter/issues/28765
    const SizedBox oversizedWidget = SizedBox(height: 80.0, width: 24.0, child: Placeholder());

    // One line
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: ListView(
            children: const <Widget>[
              ListTile(
                leading: oversizedWidget,
                title: Text('A'),
              ),
              ListTile(
                leading: oversizedWidget,
                title: Text('B'),
              ),
            ],
          ),
        ),
      ),
    );

    expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(16.0,  0.0, 24.0, 56.0));
    expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(16.0, 56.0, 24.0, 56.0));

    // Two line
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: ListView(
            children: const <Widget>[
              ListTile(
                leading: oversizedWidget,
                title: Text('A'),
                subtitle: Text('A'),
              ),
              ListTile(
                leading: oversizedWidget,
                title: Text('B'),
                subtitle: Text('B'),
              ),
            ],
          ),
        ),
      ),
    );

    expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(16.0,        8.0, 24.0, 56.0));
    expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(16.0, 72.0 + 8.0, 24.0, 56.0));

    // Three line
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: ListView(
            children: const <Widget>[
              ListTile(
                leading: oversizedWidget,
                title: Text('A'),
                subtitle: Text('A'),
                isThreeLine:  true,
              ),
              ListTile(
                leading: oversizedWidget,
                title: Text('B'),
                subtitle: Text('B'),
                isThreeLine:  true,
              ),
            ],
          ),
        ),
      ),
    );

    expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(16.0,        8.0, 24.0, 56.0));
    expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(16.0, 88.0 + 8.0, 24.0, 56.0));
  });

  testWidgets('ListTile trailing icon height does not exceed ListTile height', (WidgetTester tester) async {
    // regression test for https://github.com/flutter/flutter/issues/28765
    const SizedBox oversizedWidget = SizedBox(height: 80.0, width: 24.0, child: Placeholder());

    // One line
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: ListView(
            children: const <Widget>[
              ListTile(
                trailing: oversizedWidget,
                title: Text('A'),
                dense: false,
              ),
              ListTile(
                trailing: oversizedWidget,
                title: Text('B'),
                dense: false,
              ),
            ],
          ),
        ),
      ),
    );

    expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 24.0 - 24.0,  0.0, 24.0, 56.0));
    expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 24.0 - 24.0, 56.0, 24.0, 56.0));

    // Two line
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: ListView(
            children: const <Widget>[
              ListTile(
                trailing: oversizedWidget,
                title: Text('A'),
                subtitle: Text('A'),
                dense: false,
              ),
              ListTile(
                trailing: oversizedWidget,
                title: Text('B'),
                subtitle: Text('B'),
                dense: false,
              ),
            ],
          ),
        ),
      ),
    );

    expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 24.0 - 24.0,        8.0, 24.0, 56.0));
    expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 24.0 - 24.0, 72.0 + 8.0, 24.0, 56.0));

    // Three line
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: ListView(
            children: const <Widget>[
              ListTile(
                trailing: oversizedWidget,
                title: Text('A'),
                subtitle: Text('A'),
                isThreeLine:  true,
                dense: false,
              ),
              ListTile(
                trailing: oversizedWidget,
                title: Text('B'),
                subtitle: Text('B'),
                isThreeLine:  true,
                dense: false,
              ),
            ],
          ),
        ),
      ),
    );

    expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 24.0 - 24.0,        8.0, 24.0, 56.0));
    expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 24.0 - 24.0, 88.0 + 8.0, 24.0, 56.0));
  });

  testWidgets('ListTile only accepts focus when enabled', (WidgetTester tester) async {
    final GlobalKey childKey = GlobalKey();

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: ListView(
            children: <Widget>[
              ListTile(
                title: Text('A', key: childKey),
                dense: true,
                onTap: () {},
              ),
            ],
          ),
        ),
      ),
    );
    await tester.pump(); // Let the focus take effect.

    final FocusNode tileNode = Focus.of(childKey.currentContext!);
    tileNode.requestFocus();
    await tester.pump(); // Let the focus take effect.
    expect(Focus.of(childKey.currentContext!).hasPrimaryFocus, isTrue);

    expect(tileNode.hasPrimaryFocus, isTrue);
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: ListView(
            children: <Widget>[
              ListTile(
                title: Text('A', key: childKey),
                dense: true,
                enabled: false,
                onTap: () {},
              ),
            ],
          ),
        ),
      ),
    );

    expect(tester.binding.focusManager.primaryFocus, isNot(equals(tileNode)));
    expect(Focus.of(childKey.currentContext!).hasPrimaryFocus, isFalse);
  });

  testWidgets('ListTile can autofocus unless disabled.', (WidgetTester tester) async {
    final GlobalKey childKey = GlobalKey();

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: ListView(
            children: <Widget>[
              ListTile(
                title: Text('A', key: childKey),
                dense: true,
                autofocus: true,
                onTap: () {},
              ),
            ],
          ),
        ),
      ),
    );

    await tester.pump();
    expect(Focus.of(childKey.currentContext!).hasPrimaryFocus, isTrue);

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: ListView(
            children: <Widget>[
              ListTile(
                title: Text('A', key: childKey),
                dense: true,
                enabled: false,
                autofocus: true,
                onTap: () {},
              ),
            ],
          ),
        ),
      ),
    );

    await tester.pump();
    expect(Focus.of(childKey.currentContext!).hasPrimaryFocus, isFalse);
  });

  testWidgets('ListTile is focusable and has correct focus color', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode(debugLabel: 'ListTile');
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Container(
                width: 100,
                height: 100,
                color: Colors.white,
                child: ListTile(
                  onTap: enabled ? () {} : null,
                  focusColor: Colors.orange[500],
                  autofocus: true,
                  focusNode: focusNode,
                ),
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    await tester.pumpAndSettle();
    expect(focusNode.hasPrimaryFocus, isTrue);
    expect(
      find.byType(Material),
      paints
        ..rect()
        ..rect(
            color: Colors.orange[500],
            rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
          )
        ..rect(
            color: const Color(0xffffffff),
            rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
          ),
    );

    // Check when the list tile is disabled.
    await tester.pumpWidget(buildApp(enabled: false));
    await tester.pumpAndSettle();
    expect(focusNode.hasPrimaryFocus, isFalse);
    expect(
      find.byType(Material),
      paints
        ..rect()
        ..rect(
            color: const Color(0xffffffff),
            rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
          ),
    );
  });

  testWidgets('ListTile can be hovered and has correct hover color', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Container(
                width: 100,
                height: 100,
                color: Colors.white,
                child: ListTile(
                  onTap: enabled ? () {} : null,
                  hoverColor: Colors.orange[500],
                  autofocus: true,
                ),
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    await tester.pump();
    await tester.pumpAndSettle();
    expect(
      find.byType(Material),
      paints
        ..rect()
        ..rect(
            color: const Color(0x1f000000),
            rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
          )
        ..rect(
            color: const Color(0xffffffff),
            rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
          ),
    );

    // Start hovering
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.moveTo(tester.getCenter(find.byType(ListTile)));

    await tester.pumpWidget(buildApp());
    await tester.pump();
    await tester.pumpAndSettle();
    expect(
      find.byType(Material),
      paints
        ..rect()
        ..rect(
            color: const Color(0x1f000000),
            rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
          )
        ..rect(
            color: Colors.orange[500],
            rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
          )
        ..rect(
            color: const Color(0xffffffff),
            rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
          ),
    );

    await tester.pumpWidget(buildApp(enabled: false));
    await tester.pump();
    await tester.pumpAndSettle();
    expect(
      find.byType(Material),
      paints
        ..rect()
        ..rect(
            color: Colors.orange[500]!.withAlpha(0),
            rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
          )
        ..rect(
            color: const Color(0xffffffff),
            rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
          ),
    );
  });

  testWidgets('ListTile can be splashed and has correct splash color', (WidgetTester tester) async {
    final Widget buildApp = MaterialApp(
      theme: ThemeData(useMaterial3: false),
      home: Material(
        child: Center(
          child: SizedBox(
            width: 100,
            height: 100,
            child: ListTile(
              onTap: () {},
              splashColor: const Color(0xff88ff88),
            ),
          ),
        ),
      ),
    );

    await tester.pumpWidget(buildApp);
    await tester.pumpAndSettle();
    final TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(ListTile)).center);
    await tester.pump(const Duration(milliseconds: 200));
    expect(find.byType(Material), paints..circle(x: 50, y: 50, color: const Color(0xff88ff88)));
    await gesture.up();
  });

  testWidgets('ListTile can be triggered by keyboard shortcuts', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    const Key tileKey = Key('ListTile');
    bool tapped = false;
    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Container(
                width: 200,
                height: 100,
                color: Colors.white,
                child: ListTile(
                  key: tileKey,
                  onTap: enabled ? () {
                    setState(() {
                      tapped = true;
                    });
                  } : null,
                  hoverColor: Colors.orange[500],
                  autofocus: true,
                ),
              );
            }),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildApp());
    await tester.pumpAndSettle();

    await tester.sendKeyEvent(LogicalKeyboardKey.space);
    await tester.pumpAndSettle();

    expect(tapped, isTrue);
  });

  testWidgets('ListTile responds to density changes.', (WidgetTester tester) async {
    const Key key = Key('test');
    Future<void> buildTest(VisualDensity visualDensity) async {
      return tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: Center(
              child: ListTile(
                key: key,
                onTap: () {},
                autofocus: true,
                visualDensity: visualDensity,
              ),
            ),
          ),
        ),
      );
    }

    await buildTest(VisualDensity.standard);
    final RenderBox box = tester.renderObject(find.byKey(key));
    await tester.pumpAndSettle();
    expect(box.size, equals(const Size(800, 56)));

    await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
    await tester.pumpAndSettle();
    expect(box.size, equals(const Size(800, 68)));

    await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
    await tester.pumpAndSettle();
    expect(box.size, equals(const Size(800, 44)));

    await buildTest(const VisualDensity(horizontal: 3.0, vertical: -3.0));
    await tester.pumpAndSettle();
    expect(box.size, equals(const Size(800, 44)));
  });

  testWidgets('ListTile shape is painted correctly', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/63877
    const ShapeBorder rectShape = RoundedRectangleBorder();
    const ShapeBorder stadiumShape = StadiumBorder();
    final Color tileColor = Colors.red.shade500;

    Widget buildListTile(ShapeBorder shape) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: ListTile(shape: shape, tileColor: tileColor),
          ),
        ),
      );
    }

    // Test rectangle shape
    await tester.pumpWidget(buildListTile(rectShape));
    Rect rect = tester.getRect(find.byType(ListTile));

    // Check if a rounded rectangle was painted with the correct color and shape
    expect(
      find.byType(Material),
      paints..rect(color: tileColor, rect: rect),
    );

    // Test stadium shape
    await tester.pumpWidget(buildListTile(stadiumShape));
    rect = tester.getRect(find.byType(ListTile));

    // Check if a rounded rectangle was painted with the correct color and shape
    expect(
      find.byType(Material),
      paints..clipRect()..rrect(
        color: tileColor,
        rrect: RRect.fromRectAndRadius(rect, Radius.circular(rect.shortestSide / 2.0)),
      ),
    );
  });

  testWidgets('ListTile changes mouse cursor when hovered', (WidgetTester tester) async {
    // Test ListTile() constructor
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: Center(
            child: MouseRegion(
              cursor: SystemMouseCursors.forbidden,
              child: ListTile(
                onTap: () {},
                mouseCursor: SystemMouseCursors.text,
              ),
            ),
          ),
        ),
      ),
    );

    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
    await gesture.addPointer(location: tester.getCenter(find.byType(ListTile)));

    await tester.pump();

    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);

    // Test default cursor
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: Center(
            child: MouseRegion(
              cursor: SystemMouseCursors.forbidden,
              child: ListTile(
                onTap: () {},
              ),
            ),
          ),
        ),
      ),
    );

    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);

    // Test default cursor when disabled
    await tester.pumpWidget(
      const MaterialApp(
        home: Material(
          child: Center(
            child: MouseRegion(
              cursor: SystemMouseCursors.forbidden,
              child: ListTile(
                enabled: false,
              ),
            ),
          ),
        ),
      ),
    );

    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);

    // Test default cursor when onTap or onLongPress is null
    await tester.pumpWidget(
      const MaterialApp(
        home: Material(
          child: Center(
            child: MouseRegion(
              cursor: SystemMouseCursors.forbidden,
              child: ListTile(),
            ),
          ),
        ),
      ),
    );

    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
  });

  testWidgets('ListTile onFocusChange callback', (WidgetTester tester) async {
    final FocusNode node = FocusNode(debugLabel: 'ListTile Focus');
    bool gotFocus = false;
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: ListTile(
            focusNode: node,
            onFocusChange: (bool focused) {
              gotFocus = focused;
            },
            onTap: () {},
          ),
        ),
      ),
    );

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

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

  testWidgets('ListTile respects tileColor & selectedTileColor', (WidgetTester tester) async {
    bool isSelected = false;
    final Color tileColor = Colors.green.shade500;
    final Color selectedTileColor = Colors.red.shade500;

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                return ListTile(
                  selected: isSelected,
                  selectedTileColor: selectedTileColor,
                  tileColor: tileColor,
                  onTap: () {
                    setState(()=> isSelected = !isSelected);
                  },
                  title: const Text('Title'),
                );
              },
            ),
          ),
        ),
      ),
    );

    // Initially, when isSelected is false, the ListTile should respect tileColor.
    expect(find.byType(Material), paints..rect(color: tileColor));

    // Tap on tile to change isSelected.
    await tester.tap(find.byType(ListTile));
    await tester.pumpAndSettle();

    // When isSelected is true, the ListTile should respect selectedTileColor.
    expect(find.byType(Material), paints..rect(color: selectedTileColor));
  });

  testWidgets('ListTile shows Material ripple effects on top of tileColor', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/73616
    final Color tileColor = Colors.red.shade500;

    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: false),
        home: Material(
          child: Center(
            child: ListTile(
              tileColor: tileColor,
              onTap: () {},
              title: const Text('Title'),
            ),
          ),
        ),
      ),
    );

    // Before ListTile is tapped, it should be tileColor
    expect(find.byType(Material), paints..rect(color: tileColor));

    // Tap on tile to trigger ink effect and wait for it to be underway.
    await tester.tap(find.byType(ListTile));
    await tester.pump(const Duration(milliseconds: 200));

    // After tap, the tile could be drawn in tileColor, with the ripple (circle) on top
    expect(
      find.byType(Material),
      paints
        ..rect(color: tileColor)
        ..circle(),
    );
  });

  testWidgets('ListTile default tile color', (WidgetTester tester) async {
    bool isSelected = false;
    final ThemeData theme =  ThemeData(useMaterial3: true);
    const Color defaultColor = Colors.transparent;

    await tester.pumpWidget(
      MaterialApp(
        theme: theme,
        home: Material(
          child: Center(
            child: StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                return ListTile(
                  selected: isSelected,
                  onTap: () {
                    setState(()=> isSelected = !isSelected);
                  },
                  title: const Text('Title'),
                );
              },
            ),
          ),
        ),
      ),
    );

    expect(find.byType(Material), paints..rect(color: defaultColor));

    // Tap on tile to change isSelected.
    await tester.tap(find.byType(ListTile));
    await tester.pumpAndSettle();

    expect(find.byType(Material), paints..rect(color: defaultColor));
  });

  testWidgets('Default tile color when ListTile is wrapped with an elevated widget', (WidgetTester tester) async {
    // This is a regression test for https://github.com/flutter/flutter/issues/117700
    bool isSelected = false;
    final ThemeData theme =  ThemeData(useMaterial3: true);
    const Color defaultColor = Colors.transparent;

    await tester.pumpWidget(
      MaterialApp(
        theme: theme,
        home: Center(
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Card(
                elevation: 8.0,
                child: ListTile(
                  selected: isSelected,
                  onTap: () {
                    setState(()=> isSelected = !isSelected);
                  },
                  title: const Text('Title'),
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(
      find.byType(Material),
      paints
        ..path(color: const Color(0xff000000))
        ..path(color: const Color(0xffece6f3))
        ..save()
        ..save(),
    );
    expect(find.byType(Material), paints..rect(color: defaultColor));

    // Tap on tile to change isSelected.
    await tester.tap(find.byType(ListTile));
    await tester.pumpAndSettle();

    expect(
      find.byType(Material),
      paints
        ..path(color: const Color(0xff000000))
        ..path(color: const Color(0xffece6f3))
        ..save()
        ..save(),
    );
    expect(find.byType(Material), paints..rect(color: defaultColor));
  });

  testWidgets('ListTile layout at zero size', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/66636
    const Key key = Key('key');

    await tester.pumpWidget(const MaterialApp(
      home: Scaffold(
        body: SizedBox.shrink(
          child: ListTile(
            key: key,
            tileColor: Colors.green,
          ),
        ),
      ),
    ));

    final RenderBox renderBox = tester.renderObject(find.byKey(key));
    expect(renderBox.size.width, equals(0.0));
    expect(renderBox.size.height, equals(0.0));
  });

  group('feedback', () {
    late FeedbackTester feedback;

    setUp(() {
      feedback = FeedbackTester();
    });

    tearDown(() {
      feedback.dispose();
    });

    testWidgets('ListTile with disabled feedback', (WidgetTester tester) async {
      const bool enableFeedback = false;

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: ListTile(
              title: const Text('Title'),
              onTap: () {},
              enableFeedback: enableFeedback,
            ),
          ),
        ),
      );

      await tester.tap(find.byType(ListTile));
      await tester.pump(const Duration(seconds: 1));
      expect(feedback.clickSoundCount, 0);
      expect(feedback.hapticCount, 0);
    });

    testWidgets('ListTile with enabled feedback', (WidgetTester tester) async {
      const bool enableFeedback = true;

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: ListTile(
              title: const Text('Title'),
              onTap: () {},
              enableFeedback: enableFeedback,
            ),
          ),
        ),
      );

      await tester.tap(find.byType(ListTile));
      await tester.pump(const Duration(seconds: 1));
      expect(feedback.clickSoundCount, 1);
      expect(feedback.hapticCount, 0);
    });

    testWidgets('ListTile with enabled feedback by default', (WidgetTester tester) async {

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: ListTile(
              title: const Text('Title'),
              onTap: () {},
            ),
          ),
        ),
      );

      await tester.tap(find.byType(ListTile));
      await tester.pump(const Duration(seconds: 1));
      expect(feedback.clickSoundCount, 1);
      expect(feedback.hapticCount, 0);
    });

    testWidgets('ListTile with disabled feedback using ListTileTheme', (WidgetTester tester) async {
      const bool enableFeedbackTheme = false;

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: ListTileTheme(
              data: const ListTileThemeData(enableFeedback: enableFeedbackTheme),
              child: ListTile(
                title: const Text('Title'),
                onTap: () {},
              ),
            ),
          ),
        ),
      );

      await tester.tap(find.byType(ListTile));
      await tester.pump(const Duration(seconds: 1));
      expect(feedback.clickSoundCount, 0);
      expect(feedback.hapticCount, 0);
    });

    testWidgets('ListTile.enableFeedback overrides ListTileTheme.enableFeedback', (WidgetTester tester) async {
      const bool enableFeedbackTheme = false;
      const bool enableFeedback = true;

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: ListTileTheme(
              data: const ListTileThemeData(enableFeedback: enableFeedbackTheme),
              child: ListTile(
                enableFeedback: enableFeedback,
                title: const Text('Title'),
                onTap: () {},
              ),
            ),
          ),
        ),
      );

      await tester.tap(find.byType(ListTile));
      await tester.pump(const Duration(seconds: 1));
      expect(feedback.clickSoundCount, 1);
      expect(feedback.hapticCount, 0);
    });

    testWidgets('ListTile.mouseCursor overrides ListTileTheme.mouseCursor', (WidgetTester tester) async {
      final Key tileKey = UniqueKey();

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: ListTileTheme(
              data: const ListTileThemeData(mouseCursor: MaterialStateMouseCursor.clickable),
              child: ListTile(
                key: tileKey,
                mouseCursor: MaterialStateMouseCursor.textable,
                title: const Text('Title'),
                onTap: () {},
              ),
            ),
          ),
        ),
      );

      final Offset listTile = tester.getCenter(find.byKey(tileKey));
      final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
      await gesture.addPointer();
      await gesture.moveTo(listTile);
      await tester.pumpAndSettle();
      expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
    });
  });

  testWidgets('ListTile horizontalTitleGap = 0.0', (WidgetTester tester) async {
    Widget buildFrame(TextDirection textDirection, { double? themeHorizontalTitleGap, double? widgetHorizontalTitleGap }) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Directionality(
          textDirection: textDirection,
          child: Material(
            child: ListTileTheme(
              data: ListTileThemeData(horizontalTitleGap: themeHorizontalTitleGap),
              child: Container(
                alignment: Alignment.topLeft,
                child: ListTile(
                  horizontalTitleGap: widgetHorizontalTitleGap,
                  leading: const Text('L'),
                  title: const Text('title'),
                  trailing: const Text('T'),
                ),
              ),
            ),
          ),
        ),
      );
    }

    double left(String text) => tester.getTopLeft(find.text(text)).dx;
    double right(String text) => tester.getTopRight(find.text(text)).dx;

    await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetHorizontalTitleGap: 0));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(left('title'), 40.0);

    await tester.pumpWidget(buildFrame(TextDirection.ltr, themeHorizontalTitleGap: 0));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(left('title'), 40.0);

    await tester.pumpWidget(buildFrame(TextDirection.ltr, themeHorizontalTitleGap: 10, widgetHorizontalTitleGap: 0));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(left('title'), 40.0);

    await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetHorizontalTitleGap: 0));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(right('title'), 760.0);

    await tester.pumpWidget(buildFrame(TextDirection.rtl, themeHorizontalTitleGap: 0));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(right('title'), 760.0);

    await tester.pumpWidget(buildFrame(TextDirection.rtl, themeHorizontalTitleGap: 10, widgetHorizontalTitleGap: 0));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(right('title'), 760.0);
  });

  testWidgets('ListTile horizontalTitleGap = (default) && ListTile minLeadingWidth = (default)', (WidgetTester tester) async {
    Widget buildFrame(TextDirection textDirection) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Directionality(
          textDirection: textDirection,
          child: Material(
            child: Container(
              alignment: Alignment.topLeft,
              child: const ListTile(
                leading: Text('L'),
                title: Text('title'),
                trailing: Text('T'),
              ),
            ),
          ),
        ),
      );
    }

    double left(String text) => tester.getTopLeft(find.text(text)).dx;
    double right(String text) => tester.getTopRight(find.text(text)).dx;

    await tester.pumpWidget(buildFrame(TextDirection.ltr));

    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    // horizontalTitleGap: ListTileDefaultValue.horizontalTitleGap (16.0)
    expect(left('title'), 56.0);

    await tester.pumpWidget(buildFrame(TextDirection.rtl));

    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    // horizontalTitleGap: ListTileDefaultValue.horizontalTitleGap (16.0)
    expect(right('title'), 744.0);
  });

  testWidgets('ListTile horizontalTitleGap with visualDensity', (WidgetTester tester) async {
    Widget buildFrame({
      double? horizontalTitleGap,
      VisualDensity? visualDensity,
    }) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Container(
              alignment: Alignment.topLeft,
              child: ListTile(
                visualDensity: visualDensity,
                horizontalTitleGap: horizontalTitleGap,
                leading: const Text('L'),
                title: const Text('title'),
                trailing: const Text('T'),
              ),
            ),
          ),
        ),
      );
    }

    double left(String text) => tester.getTopLeft(find.text(text)).dx;

    await tester.pumpWidget(buildFrame(
      horizontalTitleGap: 10.0,
      visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity),
    ));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(left('title'), 42.0);

    // Pump another frame of the same widget to ensure the underlying render
    // object did not cache the original horizontalTitleGap calculation based on the
    // visualDensity
    await tester.pumpWidget(buildFrame(
      horizontalTitleGap: 10.0,
      visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity),
    ));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(left('title'), 42.0);
  });

  testWidgets('ListTile minVerticalPadding = 80.0', (WidgetTester tester) async {
    Widget buildFrame(TextDirection textDirection, { double? themeMinVerticalPadding, double? widgetMinVerticalPadding }) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Directionality(
          textDirection: textDirection,
          child: Material(
            child: ListTileTheme(
              data: ListTileThemeData(minVerticalPadding: themeMinVerticalPadding),
              child: Container(
                alignment: Alignment.topLeft,
                child: ListTile(
                  minVerticalPadding: widgetMinVerticalPadding,
                  leading: const Text('L'),
                  title: const Text('title'),
                  trailing: const Text('T'),
                ),
              ),
            ),
          ),
        ),
      );
    }


    await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetMinVerticalPadding: 80));
    // 80 + 80 + 16(Title) = 176
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 184.0));

    await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinVerticalPadding: 80));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 184.0));

    await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinVerticalPadding: 0, widgetMinVerticalPadding: 80));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 184.0));

    await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetMinVerticalPadding: 80));
    // 80 + 80 + 16(Title) = 176
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 184.0));

    await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinVerticalPadding: 80));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 184.0));

    await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinVerticalPadding: 0, widgetMinVerticalPadding: 80));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 184.0));
  });

  testWidgets('ListTile minLeadingWidth = 60.0', (WidgetTester tester) async {
    Widget buildFrame(TextDirection textDirection, { double? themeMinLeadingWidth, double? widgetMinLeadingWidth }) {
      return MediaQuery(
        data: const MediaQueryData(),
        child: Directionality(
          textDirection: textDirection,
          child: Material(
            child: ListTileTheme(
              data: ListTileThemeData(minLeadingWidth: themeMinLeadingWidth),
              child: Container(
                alignment: Alignment.topLeft,
                child: ListTile(
                  minLeadingWidth: widgetMinLeadingWidth,
                  leading: const Text('L'),
                  title: const Text('title'),
                  trailing: const Text('T'),
                ),
              ),
            ),
          ),
        ),
      );
    }

    double left(String text) => tester.getTopLeft(find.text(text)).dx;
    double right(String text) => tester.getTopRight(find.text(text)).dx;

    await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetMinLeadingWidth: 60));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    // 92.0 = 16.0(Default contentPadding) + 16.0(Default horizontalTitleGap) + 60.0
    expect(left('title'), 92.0);

    await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinLeadingWidth: 60));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(left('title'), 92.0);

    await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinLeadingWidth: 0, widgetMinLeadingWidth: 60));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(left('title'), 92.0);


    await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetMinLeadingWidth: 60));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    // 708.0 = 800.0 - (16.0(Default contentPadding) + 16.0(Default horizontalTitleGap) + 60.0)
    expect(right('title'), 708.0);

    await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinLeadingWidth: 60));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(right('title'), 708.0);

    await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinLeadingWidth: 0, widgetMinLeadingWidth: 60));
    expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
    expect(right('title'), 708.0);
  });

  testWidgets('colors are applied to leading and trailing text widgets', (WidgetTester tester) async {
    final Key leadingKey = UniqueKey();
    final Key trailingKey = UniqueKey();

    late ThemeData theme;
    Widget buildFrame({
      bool enabled = true,
      bool selected = false,
    }) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: false),
        home: Material(
          child: Center(
            child: Builder(
              builder: (BuildContext context) {
                theme = Theme.of(context);
                return ListTile(
                  enabled: enabled,
                  selected: selected,
                  leading: TestText('leading', key: leadingKey),
                  title: const TestText('title'),
                  trailing: TestText('trailing', key: trailingKey),
                );
              },
            ),
          ),
        ),
      );
    }

    Color textColor(Key key) => tester.state<TestTextState>(find.byKey(key)).textStyle.color!;

    await tester.pumpWidget(buildFrame());
    // Enabled color should be default bodyMedium color.
    expect(textColor(leadingKey), theme.textTheme.bodyMedium!.color);
    expect(textColor(trailingKey), theme.textTheme.bodyMedium!.color);

    await tester.pumpWidget(buildFrame(selected: true));
    // Wait for text color to animate.
    await tester.pumpAndSettle();
    // Selected color should be ThemeData.primaryColor by default.
    expect(textColor(leadingKey), theme.primaryColor);
    expect(textColor(trailingKey), theme.primaryColor);

    await tester.pumpWidget(buildFrame(enabled: false));
    // Wait for text color to animate.
    await tester.pumpAndSettle();
    // Disabled color should be ThemeData.disabledColor by default.
    expect(textColor(leadingKey), theme.disabledColor);
    expect(textColor(trailingKey), theme.disabledColor);
  });

  testWidgets('selected, enabled ListTile default icon color', (WidgetTester tester) async {
    final ThemeData theme = ThemeData(useMaterial3: true);
    final ColorScheme colorScheme = theme.colorScheme;
    final Key leadingKey = UniqueKey();
    final Key titleKey = UniqueKey();
    final Key subtitleKey = UniqueKey();
    final Key trailingKey = UniqueKey();

    Widget buildFrame({required bool selected }) {
      return MaterialApp(
        theme: theme,
        home: Material(
          child: Center(
            child: ListTile(
              selected: selected,
              leading: TestIcon(key: leadingKey),
              title: TestIcon(key: titleKey),
              subtitle: TestIcon(key: subtitleKey),
              trailing: TestIcon(key: trailingKey),
            ),
          ),
        ),
      );
    }

    Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;

    await tester.pumpWidget(buildFrame(selected: true));
    expect(iconColor(leadingKey), colorScheme.primary);
    expect(iconColor(titleKey), colorScheme.primary);
    expect(iconColor(subtitleKey), colorScheme.primary);
    expect(iconColor(trailingKey), colorScheme.primary);

    await tester.pumpWidget(buildFrame(selected: false));
    expect(iconColor(leadingKey), colorScheme.onSurfaceVariant);
    expect(iconColor(titleKey), colorScheme.onSurfaceVariant);
    expect(iconColor(subtitleKey), colorScheme.onSurfaceVariant);
    expect(iconColor(trailingKey), colorScheme.onSurfaceVariant);
  });

  testWidgets('ListTile font size', (WidgetTester tester) async {
    Widget buildFrame() {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: Center(
            child: Builder(
              builder: (BuildContext context) {
                return const ListTile(
                  leading: TestText('leading'),
                  title: TestText('title'),
                  subtitle: TestText('subtitle') ,
                  trailing: TestText('trailing'),
                );
              },
            ),
          ),
        ),
      );
    }

    // ListTile default text sizes.
    await tester.pumpWidget(buildFrame());
    final RenderParagraph leading = _getTextRenderObject(tester, 'leading');
    expect(leading.text.style!.fontSize, 11.0);
    final RenderParagraph title = _getTextRenderObject(tester, 'title');
    expect(title.text.style!.fontSize, 16.0);
    final RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
    expect(subtitle.text.style!.fontSize, 14.0);
    final RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
    expect(trailing.text.style!.fontSize, 11.0);
  });

  testWidgets('ListTile text color', (WidgetTester tester) async {
    Widget buildFrame() {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: Center(
            child: Builder(
              builder: (BuildContext context) {
                return const ListTile(
                  leading: TestText('leading'),
                  title: TestText('title'),
                  subtitle: TestText('subtitle') ,
                  trailing: TestText('trailing'),
                );
              },
            ),
          ),
        ),
      );
    }

    final ThemeData theme = ThemeData(useMaterial3: true);

    // ListTile default text colors.
    await tester.pumpWidget(buildFrame());
    final RenderParagraph leading = _getTextRenderObject(tester, 'leading');
    expect(leading.text.style!.color, theme.colorScheme.onSurfaceVariant);
    final RenderParagraph title = _getTextRenderObject(tester, 'title');
    expect(title.text.style!.color, theme.colorScheme.onSurface);
    final RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
    expect(subtitle.text.style!.color, theme.colorScheme.onSurfaceVariant);
    final RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
    expect(trailing.text.style!.color, theme.colorScheme.onSurfaceVariant);
  });

  testWidgets('Default ListTile debugFillProperties', (WidgetTester tester) async {
    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
    const ListTile().debugFillProperties(builder);

    final List<String> description = builder.properties
      .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
      .map((DiagnosticsNode node) => node.toString())
      .toList();

    expect(description, <String>[]);
  });

  testWidgets('ListTile implements debugFillProperties', (WidgetTester tester) async {
    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
    const ListTile(
      leading: Text('leading'),
      title: Text('title'),
      subtitle: Text('trailing'),
      trailing: Text('trailing'),
      isThreeLine: true,
      dense: true,
      visualDensity: VisualDensity.standard,
      shape: RoundedRectangleBorder(),
      style: ListTileStyle.list,
      selectedColor: Color(0xff0000ff),
      iconColor: Color(0xff00ff00),
      textColor: Color(0xffff0000),
      titleTextStyle: TextStyle(fontSize: 22),
      subtitleTextStyle: TextStyle(fontSize: 18),
      leadingAndTrailingTextStyle: TextStyle(fontSize: 16),
      contentPadding: EdgeInsets.zero,
      enabled: false,
      selected: true,
      focusColor: Color(0xff00ffff),
      hoverColor: Color(0xff0000ff),
      autofocus: true,
      tileColor: Color(0xffffff00),
      selectedTileColor: Color(0xff123456),
      enableFeedback: false,
      horizontalTitleGap: 4.0,
      minVerticalPadding: 2.0,
      minLeadingWidth: 6.0,
      titleAlignment: ListTileTitleAlignment.bottom,
    ).debugFillProperties(builder);

    final List<String> description = builder.properties
      .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
      .map((DiagnosticsNode node) => node.toString())
      .toList();

    expect(
      description,
      equalsIgnoringHashCodes(<String>[
        'leading: Text',
        'title: Text',
        'subtitle: Text',
        'trailing: Text',
        'isThreeLine: THREE_LINE',
        'dense: true',
        'visualDensity: VisualDensity#00000(h: 0.0, v: 0.0)',
        'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)',
        'style: ListTileStyle.list',
        'selectedColor: Color(0xff0000ff)',
        'iconColor: Color(0xff00ff00)',
        'textColor: Color(0xffff0000)',
        'titleTextStyle: TextStyle(inherit: true, size: 22.0)',
        'subtitleTextStyle: TextStyle(inherit: true, size: 18.0)',
        'leadingAndTrailingTextStyle: TextStyle(inherit: true, size: 16.0)',
        'contentPadding: EdgeInsets.zero',
        'enabled: false',
        'selected: true',
        'focusColor: Color(0xff00ffff)',
        'hoverColor: Color(0xff0000ff)',
        'autofocus: true',
        'tileColor: Color(0xffffff00)',
        'selectedTileColor: Color(0xff123456)',
        'enableFeedback: false',
        'horizontalTitleGap: 4.0',
        'minVerticalPadding: 2.0',
        'minLeadingWidth: 6.0',
        'titleAlignment: ListTileTitleAlignment.bottom',
      ]),
    );
  });

  testWidgets('ListTile.textColor respects MaterialStateColor', (WidgetTester tester) async {
    bool enabled = false;
    bool selected = false;
    const Color defaultColor = Colors.blue;
    const Color selectedColor = Colors.green;
    const Color disabledColor = Colors.red;

    Widget buildFrame() {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: Center(
            child: Builder(
              builder: (BuildContext context) {
                return ListTile(
                  enabled: enabled,
                  selected: selected,
                  textColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
                    if (states.contains(MaterialState.disabled)) {
                      return disabledColor;
                    }
                    if (states.contains(MaterialState.selected)) {
                      return selectedColor;
                    }
                    return defaultColor;
                  }),
                  title: const TestText('title'),
                  subtitle: const TestText('subtitle') ,
                );
              },
            ),
          ),
        ),
      );
    }

    // Test disabled state.
    await tester.pumpWidget(buildFrame());
    RenderParagraph title = _getTextRenderObject(tester, 'title');
    expect(title.text.style!.color, disabledColor);

    // Test enabled state.
    enabled = true;
    await tester.pumpWidget(buildFrame());
    await tester.pumpAndSettle();
    title = _getTextRenderObject(tester, 'title');
    expect(title.text.style!.color, defaultColor);

    // Test selected state.
    selected = true;
    await tester.pumpWidget(buildFrame());
    await tester.pumpAndSettle();
    title = _getTextRenderObject(tester, 'title');
    expect(title.text.style!.color, selectedColor);
  });

  testWidgets('ListTile.iconColor respects MaterialStateColor', (WidgetTester tester) async {
    bool enabled = false;
    bool selected = false;
    const Color defaultColor = Colors.blue;
    const Color selectedColor = Colors.green;
    const Color disabledColor = Colors.red;
    final Key leadingKey = UniqueKey();

    Widget buildFrame() {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: Center(
            child: Builder(
              builder: (BuildContext context) {
                return ListTile(
                  enabled: enabled,
                  selected: selected,
                  iconColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
                    if (states.contains(MaterialState.disabled)) {
                      return disabledColor;
                    }
                    if (states.contains(MaterialState.selected)) {
                      return selectedColor;
                    }
                    return defaultColor;
                  }),
                  leading: TestIcon(key: leadingKey),
                );
              },
            ),
          ),
        ),
      );
    }

    Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;

    // Test disabled state.
    await tester.pumpWidget(buildFrame());
    expect(iconColor(leadingKey), disabledColor);

    // Test enabled state.
    enabled = true;
    await tester.pumpWidget(buildFrame());
    await tester.pumpAndSettle();
    expect(iconColor(leadingKey), defaultColor);

    // Test selected state.
    selected = true;
    await tester.pumpWidget(buildFrame());
    await tester.pumpAndSettle();
    expect(iconColor(leadingKey), selectedColor);
  });

  testWidgets('ListTile.iconColor respects iconColor property with icon buttons Material 3 in presence of IconButtonTheme override', (WidgetTester tester) async {
    const Color iconButtonThemeColor = Colors.blue;
    const Color listTileIconColor = Colors.green;
    const Icon leadingIcon = Icon(Icons.favorite);
    const Icon trailingIcon = Icon(Icons.close);

    Widget buildFrame() {
      return MaterialApp(
        theme: ThemeData(
          useMaterial3: true,
          iconButtonTheme: IconButtonThemeData(
            style:  IconButton.styleFrom(
              foregroundColor: iconButtonThemeColor,
            ),
          ),
        ),
        home: Material(
          child: Center(
            child: Builder(
              builder: (BuildContext context) {
                return ListTile(
                  iconColor: listTileIconColor,
                  leading: IconButton(icon: leadingIcon, onPressed: () {}),
                  trailing: IconButton(icon: trailingIcon, onPressed: () {}),
                );
              },
            ),
          ),
        ),
      );
    }

    TextStyle? getIconStyle(WidgetTester tester, IconData icon) =>
      tester.widget<RichText>(find.descendant(
        of: find.byIcon(icon),
        matching: find.byType(RichText),
      ),
    ).text.style;

    await tester.pumpWidget(buildFrame());
    expect(getIconStyle(tester, leadingIcon.icon!)?.color, listTileIconColor);
    expect(getIconStyle(tester, trailingIcon.icon!)?.color, listTileIconColor);
  });

  testWidgets('ListTile.dense does not throw assertion', (WidgetTester tester) async {
    // This is a regression test for https://github.com/flutter/flutter/pull/116908

    Widget buildFrame({required bool useMaterial3}) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: useMaterial3),
        home: Material(
          child: Center(
            child: StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                return const ListTile(
                  dense: true,
                  title: Text('Title'),
                );
              },
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildFrame(useMaterial3: false));
    expect(tester.takeException(), isNull);

    await tester.pumpWidget(buildFrame(useMaterial3: true));
    expect(tester.takeException(), isNull);
  });

  testWidgets('titleAlignment position with title widget', (WidgetTester tester) async {
    final Key leadingKey = GlobalKey();
    final Key trailingKey = GlobalKey();
    const double leadingHeight = 24.0;
    const double titleHeight = 50.0;
    const double trailingHeight = 24.0;
    const double minVerticalPadding = 10.0;
    const double tileHeight = minVerticalPadding * 2 + titleHeight;

    Widget buildFrame({ ListTileTitleAlignment? titleAlignment }) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: Center(
            child: ListTile(
              titleAlignment: titleAlignment,
              minVerticalPadding: minVerticalPadding,
              leading: SizedBox(key: leadingKey, width: 24.0, height: leadingHeight),
              title: const SizedBox(width: 20.0, height: titleHeight),
              trailing: SizedBox(key: trailingKey, width: 24.0, height: trailingHeight),
            ),
          ),
        ),
      );
    }

    // If [ThemeData.useMaterial3] is true, the default title alignment is
    // [ListTileTitleAlignment.threeLine], which positions the leading and
    // trailing widgets center vertically in the tile if the [ListTile.isThreeLine]
    // property is false.
    await tester.pumpWidget(buildFrame());
    Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
    Offset leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    Offset trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are centered vertically in the tile.
    const double centerPosition = (tileHeight / 2) - (leadingHeight / 2);
    expect(leadingOffset.dy - tileOffset.dy, centerPosition);
    expect(trailingOffset.dy - tileOffset.dy, centerPosition);

    // Test [ListTileTitleAlignment.threeLine] alignment.
    await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.threeLine));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are centered vertically in the tile,
    // If the [ListTile.isThreeLine] property is false.
    expect(leadingOffset.dy - tileOffset.dy, centerPosition);
    expect(trailingOffset.dy - tileOffset.dy, centerPosition);

    // Test [ListTileTitleAlignment.titleHeight] alignment.
    await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.titleHeight));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // If the tile height is less than 72.0 pixels, the leading widget is placed
    // 16.0 pixels below the top of the title widget, and the trailing is centered
    // vertically in the tile.
    const double titlePosition = 16.0;
    expect(leadingOffset.dy - tileOffset.dy, titlePosition);
    expect(trailingOffset.dy - tileOffset.dy, centerPosition);

    // Test [ListTileTitleAlignment.top] alignment.
    await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.top));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are placed minVerticalPadding below
    // the top of the title widget.
    const double topPosition = minVerticalPadding;
    expect(leadingOffset.dy - tileOffset.dy, topPosition);
    expect(trailingOffset.dy - tileOffset.dy, topPosition);

    // Test [ListTileTitleAlignment.center] alignment.
    await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.center));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are centered vertically in the tile.
    expect(leadingOffset.dy - tileOffset.dy, centerPosition);
    expect(trailingOffset.dy - tileOffset.dy, centerPosition);

    // Test [ListTileTitleAlignment.bottom] alignment.
    await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.bottom));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are placed minVerticalPadding above
    // the bottom of the subtitle widget.
    const double bottomPosition = tileHeight - minVerticalPadding - leadingHeight;
    expect(leadingOffset.dy - tileOffset.dy, bottomPosition);
    expect(trailingOffset.dy - tileOffset.dy, bottomPosition);
  });

  testWidgets('titleAlignment position with title and subtitle widgets', (WidgetTester tester) async {
    final Key leadingKey = GlobalKey();
    final Key trailingKey = GlobalKey();
    const double leadingHeight = 24.0;
    const double titleHeight = 50.0;
    const double subtitleHeight = 50.0;
    const double trailingHeight = 24.0;
    const double minVerticalPadding = 10.0;
    const double tileHeight = minVerticalPadding * 2 + titleHeight + subtitleHeight;

    Widget buildFrame({ ListTileTitleAlignment? titleAlignment }) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: Center(
            child: ListTile(
              titleAlignment: titleAlignment,
              minVerticalPadding: minVerticalPadding,
              leading: SizedBox(key: leadingKey, width: 24.0, height: leadingHeight),
              title: const SizedBox(width: 20.0, height: titleHeight),
              subtitle: const SizedBox(width: 20.0, height: subtitleHeight),
              trailing: SizedBox(key: trailingKey, width: 24.0, height: trailingHeight),
            ),
          ),
        ),
      );
    }

    // If [ThemeData.useMaterial3] is true, the default title alignment is
    // [ListTileTitleAlignment.threeLine], which positions the leading and
    // trailing widgets center vertically in the tile if the [ListTile.isThreeLine]
    // property is false.
    await tester.pumpWidget(buildFrame());
    Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
    Offset leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    Offset trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are centered vertically in the tile.
    const double centerPosition = (tileHeight / 2) - (leadingHeight / 2);
    expect(leadingOffset.dy - tileOffset.dy, centerPosition);
    expect(trailingOffset.dy - tileOffset.dy, centerPosition);

    // Test [ListTileTitleAlignment.threeLine] alignment.
    await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.threeLine));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are centered vertically in the tile,
    // If the [ListTile.isThreeLine] property is false.
    expect(leadingOffset.dy - tileOffset.dy, centerPosition);
    expect(trailingOffset.dy - tileOffset.dy, centerPosition);

    // Test [ListTileTitleAlignment.titleHeight] alignment.
    await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.titleHeight));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are positioned 16.0 pixels below the
    // top of the title widget.
    const double titlePosition = 16.0;
    expect(leadingOffset.dy - tileOffset.dy, titlePosition);
    expect(trailingOffset.dy - tileOffset.dy, titlePosition);

    // Test [ListTileTitleAlignment.top] alignment.
    await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.top));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are placed minVerticalPadding below
    // the top of the title widget.
    const double topPosition = minVerticalPadding;
    expect(leadingOffset.dy - tileOffset.dy, topPosition);
    expect(trailingOffset.dy - tileOffset.dy, topPosition);

    // Test [ListTileTitleAlignment.center] alignment.
    await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.center));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are centered vertically in the tile.
    expect(leadingOffset.dy - tileOffset.dy, centerPosition);
    expect(trailingOffset.dy - tileOffset.dy, centerPosition);

    // Test [ListTileTitleAlignment.bottom] alignment.
    await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.bottom));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // Leading and trailing widgets are placed minVerticalPadding above
    // the bottom of the subtitle widget.
    const double bottomPosition = tileHeight - minVerticalPadding - leadingHeight;
    expect(leadingOffset.dy - tileOffset.dy, bottomPosition);
    expect(trailingOffset.dy - tileOffset.dy, bottomPosition);
  });

  testWidgets("ListTile.isThreeLine updates ListTileTitleAlignment.threeLine's alignment", (WidgetTester tester) async {
    final Key leadingKey = GlobalKey();
    final Key trailingKey = GlobalKey();
    const double leadingHeight = 24.0;
    const double titleHeight = 50.0;
    const double subtitleHeight = 50.0;
    const double trailingHeight = 24.0;
    const double minVerticalPadding = 10.0;
    const double tileHeight = minVerticalPadding * 2 + titleHeight + subtitleHeight;

    Widget buildFrame({ ListTileTitleAlignment? titleAlignment, bool isThreeLine = false }) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: Center(
            child: ListTile(
              titleAlignment: titleAlignment,
              minVerticalPadding: minVerticalPadding,
              leading: SizedBox(key: leadingKey, width: 24.0, height: leadingHeight),
              title: const SizedBox(width: 20.0, height: titleHeight),
              subtitle: const SizedBox(width: 20.0, height: subtitleHeight),
              trailing: SizedBox(key: trailingKey, width: 24.0, height: trailingHeight),
              isThreeLine: isThreeLine,
            ),
          ),
        ),
      );
    }

    // If [ThemeData.useMaterial3] is true, then title alignment should
    // default to [ListTileTitleAlignment.threeLine].
    await tester.pumpWidget(buildFrame());
    Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
    Offset leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    Offset trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // By default, leading and trailing widgets are centered vertically
    // in the tile.
    const double centerPosition = (tileHeight / 2) - (leadingHeight / 2);
    expect(leadingOffset.dy - tileOffset.dy, centerPosition);
    expect(trailingOffset.dy - tileOffset.dy, centerPosition);

    // Set [ListTile.isThreeLine] to true to update the alignment.
    await tester.pumpWidget(buildFrame(isThreeLine: true));
    tileOffset = tester.getTopLeft(find.byType(ListTile));
    leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
    trailingOffset = tester.getTopRight(find.byKey(trailingKey));

    // The leading and trailing widgets are placed minVerticalPadding
    // to the top of the tile widget.
    const double topPosition = minVerticalPadding;
    expect(leadingOffset.dy - tileOffset.dy, topPosition);
    expect(trailingOffset.dy - tileOffset.dy, topPosition);
  });

  group('Material 2', () {
    // These tests are only relevant for Material 2. Once Material 2
    // support is deprecated and the APIs are removed, these tests
    // can be deleted.

    testWidgets('ListTile geometry (LTR)', (WidgetTester tester) async {
      // See https://material.io/go/design-lists

      final Key leadingKey = GlobalKey();
      final Key trailingKey = GlobalKey();
      late bool hasSubtitle;

      const double leftPadding = 10.0;
      const double rightPadding = 20.0;
      Widget buildFrame({ bool dense = false, bool isTwoLine = false, bool isThreeLine = false, double textScaleFactor = 1.0, double? subtitleScaleFactor }) {
        hasSubtitle = isTwoLine || isThreeLine;
        subtitleScaleFactor ??= textScaleFactor;
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: MediaQuery(
            data: MediaQueryData(
              padding: const EdgeInsets.only(left: leftPadding, right: rightPadding),
              textScaleFactor: textScaleFactor,
            ),
            child: Material(
              child: Center(
                child: ListTile(
                  leading: SizedBox(key: leadingKey, width: 24.0, height: 24.0),
                  title: const Text('title'),
                  subtitle: hasSubtitle ? Text('subtitle', textScaleFactor: subtitleScaleFactor) : null,
                  trailing: SizedBox(key: trailingKey, width: 24.0, height: 24.0),
                  dense: dense,
                  isThreeLine: isThreeLine,
                ),
              ),
            ),
          ),
        );
      }

      void testChildren() {
        expect(find.byKey(leadingKey), findsOneWidget);
        expect(find.text('title'), findsOneWidget);
        if (hasSubtitle) {
          expect(find.text('subtitle'), findsOneWidget);
        }
        expect(find.byKey(trailingKey), findsOneWidget);
      }

      double left(String text) => tester.getTopLeft(find.text(text)).dx;
      double top(String text) => tester.getTopLeft(find.text(text)).dy;
      double bottom(String text) => tester.getBottomLeft(find.text(text)).dy;
      double height(String text) => tester.getRect(find.text(text)).height;

      double leftKey(Key key) => tester.getTopLeft(find.byKey(key)).dx;
      double rightKey(Key key) => tester.getTopRight(find.byKey(key)).dx;
      double widthKey(Key key) => tester.getSize(find.byKey(key)).width;
      double heightKey(Key key) => tester.getSize(find.byKey(key)).height;

      // ListTiles are contained by a SafeArea defined like this:
      // SafeArea(top: false, bottom: false, minimum: contentPadding)
      // The default contentPadding is 16.0 on the left and right.
      void testHorizontalGeometry() {
        expect(leftKey(leadingKey), math.max(16.0, leftPadding));
        expect(left('title'), 56.0 + math.max(16.0, leftPadding));
        if (hasSubtitle) {
          expect(left('subtitle'), 56.0 + math.max(16.0, leftPadding));
        }
        expect(left('title'), rightKey(leadingKey) + 32.0);
        expect(rightKey(trailingKey), 800.0 - math.max(16.0, rightPadding));
        expect(widthKey(trailingKey), 24.0);
      }

      void testVerticalGeometry(double expectedHeight) {
        final Rect tileRect = tester.getRect(find.byType(ListTile));
        expect(tileRect.size, Size(800.0, expectedHeight));
        expect(top('title'), greaterThanOrEqualTo(tileRect.top));
        if (hasSubtitle) {
          expect(top('subtitle'), greaterThanOrEqualTo(bottom('title')));
          expect(bottom('subtitle'), lessThan(tileRect.bottom));
        } else {
          expect(top('title'), equals(tileRect.top + (tileRect.height - height('title')) / 2.0));
        }
        expect(heightKey(trailingKey), 24.0);
      }

      await tester.pumpWidget(buildFrame());
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(56.0);

      await tester.pumpWidget(buildFrame(dense: true));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(48.0);

      await tester.pumpWidget(buildFrame(isTwoLine: true));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(72.0);

      await tester.pumpWidget(buildFrame(isTwoLine: true, dense: true));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(64.0);

      await tester.pumpWidget(buildFrame(isThreeLine: true));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(88.0);

      await tester.pumpWidget(buildFrame(isThreeLine: true, dense: true));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(76.0);

      await tester.pumpWidget(buildFrame(textScaleFactor: 4.0));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(72.0);

      await tester.pumpWidget(buildFrame(dense: true, textScaleFactor: 4.0));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(72.0);

      await tester.pumpWidget(buildFrame(isTwoLine: true, textScaleFactor: 4.0));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(128.0);

      // Make sure that the height of a large subtitle is taken into account.
      await tester.pumpWidget(buildFrame(isTwoLine: true, textScaleFactor: 0.5, subtitleScaleFactor: 4.0));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(72.0);

      await tester.pumpWidget(buildFrame(isTwoLine: true, dense: true, textScaleFactor: 4.0));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(128.0);

      await tester.pumpWidget(buildFrame(isThreeLine: true, textScaleFactor: 4.0));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(128.0);

      await tester.pumpWidget(buildFrame(isThreeLine: true, dense: true, textScaleFactor: 4.0));
      testChildren();
      testHorizontalGeometry();
      testVerticalGeometry(128.0);
    });

    testWidgets('ListTile geometry (RTL)', (WidgetTester tester) async {
      const double leftPadding = 10.0;
      const double rightPadding = 20.0;
      await tester.pumpWidget(MaterialApp(
        theme: ThemeData(useMaterial3: false),
        home: const MediaQuery(
          data: MediaQueryData(
            padding: EdgeInsets.only(left: leftPadding, right: rightPadding),
          ),
          child: Directionality(
            textDirection: TextDirection.rtl,
            child: Material(
              child: Center(
                child: ListTile(
                  leading: Text('L'),
                  title: Text('title'),
                  trailing: Text('T'),
                ),
              ),
            ),
          ),
        ),
      ));

      double left(String text) => tester.getTopLeft(find.text(text)).dx;
      double right(String text) => tester.getTopRight(find.text(text)).dx;

      void testHorizontalGeometry() {
        expect(right('L'), 800.0 - math.max(16.0, rightPadding));
        expect(right('title'), 800.0 - 56.0 - math.max(16.0, rightPadding));
        expect(left('T'), math.max(16.0, leftPadding));
      }

      testHorizontalGeometry();
    });

    testWidgets('ListTile leading and trailing positions', (WidgetTester tester) async {
      // This test is based on the redlines at
      // https://material.io/design/components/lists.html#specs

      // DENSE "ONE"-LINE
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  dense: true,
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
                ),
                ListTile(
                  dense: true,
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                ),
              ],
            ),
          ),
        ),
      );
      //                                                                          LEFT                  TOP          WIDTH  HEIGHT
      expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,          0.0, 800.0, 177.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(0)), const Rect.fromLTWH(               16.0,         16.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0,         16.0,  24.0,  24.0));
      expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0,        177.0, 800.0,  48.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(1)), const Rect.fromLTWH(               16.0, 177.0 +  4.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0, 177.0 + 12.0,  24.0,  24.0));

      // NON-DENSE "ONE"-LINE
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
                ),
                ListTile(
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                ),
              ],
            ),
          ),
        ),
      );
      await tester.pump(const Duration(seconds: 2)); // the text styles are animated when we change dense
      //                                                                          LEFT                 TOP                   WIDTH  HEIGHT
      expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,          0.0, 800.0, 216.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(0)), const Rect.fromLTWH(               16.0,         16.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0,         16.0,  24.0,  24.0));
      expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0, 216.0       , 800.0,  56.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(1)), const Rect.fromLTWH(               16.0, 216.0 +  8.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0, 216.0 + 16.0,  24.0,  24.0));

      // DENSE "TWO"-LINE
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  dense: true,
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                  subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
                ),
                ListTile(
                  dense: true,
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                  subtitle: Text('A'),
                ),
              ],
            ),
          ),
        ),
      );
      //                                                                          LEFT                 TOP          WIDTH  HEIGHT
      expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,          0.0, 800.0, 180.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(0)), const Rect.fromLTWH(               16.0,         16.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0,         16.0,  24.0,  24.0));
      expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0, 180.0,        800.0,  64.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(1)), const Rect.fromLTWH(               16.0, 180.0 + 12.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 20.0,  24.0,  24.0));

      // NON-DENSE "TWO"-LINE
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                  subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
                ),
                ListTile(
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                  subtitle: Text('A'),
                ),
              ],
            ),
          ),
        ),
      );
      //                                                                          LEFT                 TOP          WIDTH  HEIGHT
      expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,          0.0, 800.0, 180.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(0)), const Rect.fromLTWH(               16.0,         16.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0,         16.0,  24.0,  24.0));
      expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0, 180.0,        800.0,  72.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(1)), const Rect.fromLTWH(               16.0, 180.0 + 16.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 24.0,  24.0,  24.0));

      // DENSE "THREE"-LINE
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  dense: true,
                  isThreeLine: true,
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                  subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
                ),
                ListTile(
                  dense: true,
                  isThreeLine: true,
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                  subtitle: Text('A'),
                ),
              ],
            ),
          ),
        ),
      );
      //                                                                          LEFT                 TOP          WIDTH  HEIGHT
      expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,          0.0, 800.0, 180.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(0)), const Rect.fromLTWH(               16.0,         16.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0,         16.0,  24.0,  24.0));
      expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0, 180.0,        800.0,  76.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(1)), const Rect.fromLTWH(               16.0, 180.0 + 16.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 16.0,  24.0,  24.0));

      // NON-DENSE THREE-LINE
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  isThreeLine: true,
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                  subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
                ),
                ListTile(
                  isThreeLine: true,
                  leading: CircleAvatar(),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                  subtitle: Text('A'),
                ),
              ],
            ),
          ),
        ),
      );
      //                                                                          LEFT                 TOP          WIDTH  HEIGHT
      expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,          0.0, 800.0, 180.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(0)), const Rect.fromLTWH(               16.0,         16.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0,         16.0,  24.0,  24.0));
      expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0, 180.0,        800.0,  88.0));
      expect(tester.getRect(find.byType(CircleAvatar).at(1)), const Rect.fromLTWH(               16.0, 180.0 + 16.0,  40.0,  40.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 16.0,  24.0,  24.0));

      // "ONE-LINE" with Small Leading Widget
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  leading: SizedBox(height:12.0, width:24.0, child: Placeholder()),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
                ),
                ListTile(
                  leading: SizedBox(height:12.0, width:24.0, child: Placeholder()),
                  trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
                  title: Text('A'),
                ),
              ],
            ),
          ),
        ),
      );
      await tester.pump(const Duration(seconds: 2)); // the text styles are animated when we change dense
      //                                                                          LEFT                 TOP           WIDTH  HEIGHT
      expect(tester.getRect(find.byType(ListTile).at(0)),     const Rect.fromLTWH(                0.0,          0.0, 800.0, 216.0));
      expect(tester.getRect(find.byType(Placeholder).at(0)),  const Rect.fromLTWH(               16.0,         16.0,  24.0,  12.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0,         16.0,  24.0,  24.0));
      expect(tester.getRect(find.byType(ListTile).at(1)),     const Rect.fromLTWH(                0.0, 216.0       , 800.0,  56.0));
      expect(tester.getRect(find.byType(Placeholder).at(2)),  const Rect.fromLTWH(               16.0, 216.0 + 16.0,  24.0,  12.0));
      expect(tester.getRect(find.byType(Placeholder).at(3)),  const Rect.fromLTWH(800.0 - 24.0 - 16.0, 216.0 + 16.0,  24.0,  24.0));
    });

    testWidgets('ListTile leading icon height does not exceed ListTile height', (WidgetTester tester) async {
      // regression test for https://github.com/flutter/flutter/issues/28765
      const SizedBox oversizedWidget = SizedBox(height: 80.0, width: 24.0, child: Placeholder());

      // Dense One line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  leading: oversizedWidget,
                  title: Text('A'),
                  dense: true,
                ),
                ListTile(
                  leading: oversizedWidget,
                  title: Text('B'),
                  dense: true,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(16.0,  0.0, 24.0, 48.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(16.0, 48.0, 24.0, 48.0));

      // Non-dense One line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  leading: oversizedWidget,
                  title: Text('A'),
                  dense: false,
                ),
                ListTile(
                  leading: oversizedWidget,
                  title: Text('B'),
                  dense: false,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(16.0,  0.0, 24.0, 56.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(16.0, 56.0, 24.0, 56.0));

      // Dense Two line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  leading: oversizedWidget,
                  title: Text('A'),
                  subtitle: Text('A'),
                  dense: true,
                ),
                ListTile(
                  leading: oversizedWidget,
                  title: Text('B'),
                  subtitle: Text('B'),
                  dense: true,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(16.0,        8.0, 24.0, 48.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(16.0, 64.0 + 8.0, 24.0, 48.0));

      // Non-dense Two line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  leading: oversizedWidget,
                  title: Text('A'),
                  subtitle: Text('A'),
                  dense: false,
                ),
                ListTile(
                  leading: oversizedWidget,
                  title: Text('B'),
                  subtitle: Text('B'),
                  dense: false,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(16.0,        8.0, 24.0, 56.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(16.0, 72.0 + 8.0, 24.0, 56.0));

      // Dense Three line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  leading: oversizedWidget,
                  title: Text('A'),
                  subtitle: Text('A'),
                  isThreeLine:  true,
                  dense: true,
                ),
                ListTile(
                  leading: oversizedWidget,
                  title: Text('B'),
                  subtitle: Text('B'),
                  isThreeLine:  true,
                  dense: true,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(16.0,        16.0, 24.0, 48.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(16.0, 76.0 + 16.0, 24.0, 48.0));

      // Non-dense Three line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  leading: oversizedWidget,
                  title: Text('A'),
                  subtitle: Text('A'),
                  isThreeLine:  true,
                  dense: false,
                ),
                ListTile(
                  leading: oversizedWidget,
                  title: Text('B'),
                  subtitle: Text('B'),
                  isThreeLine:  true,
                  dense: false,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(16.0,        16.0, 24.0, 56.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(16.0, 88.0 + 16.0, 24.0, 56.0));
    });

    testWidgets('ListTile trailing icon height does not exceed ListTile height', (WidgetTester tester) async {
      // regression test for https://github.com/flutter/flutter/issues/28765
      const SizedBox oversizedWidget = SizedBox(height: 80.0, width: 24.0, child: Placeholder());

      // Dense One line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('A'),
                  dense: true,
                ),
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('B'),
                  dense: true,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 16.0 - 24.0,    0, 24.0, 48.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 16.0 - 24.0, 48.0, 24.0, 48.0));

      // Non-dense One line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('A'),
                  dense: false,
                ),
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('B'),
                  dense: false,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 16.0 - 24.0,  0.0, 24.0, 56.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 16.0 - 24.0, 56.0, 24.0, 56.0));

      // Dense Two line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('A'),
                  subtitle: Text('A'),
                  dense: true,
                ),
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('B'),
                  subtitle: Text('B'),
                  dense: true,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 16.0 - 24.0,        8.0, 24.0, 48.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 16.0 - 24.0, 64.0 + 8.0, 24.0, 48.0));

      // Non-dense Two line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('A'),
                  subtitle: Text('A'),
                  dense: false,
                ),
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('B'),
                  subtitle: Text('B'),
                  dense: false,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 16.0 - 24.0,        8.0, 24.0, 56.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 16.0 - 24.0, 72.0 + 8.0, 24.0, 56.0));

      // Dense Three line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('A'),
                  subtitle: Text('A'),
                  isThreeLine:  true,
                  dense: true,
                ),
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('B'),
                  subtitle: Text('B'),
                  isThreeLine:  true,
                  dense: true,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 16.0 - 24.0,        16.0, 24.0, 48.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 16.0 - 24.0, 76.0 + 16.0, 24.0, 48.0));

      // Non-dense Three line
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: ListView(
              children: const <Widget>[
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('A'),
                  subtitle: Text('A'),
                  isThreeLine:  true,
                  dense: false,
                ),
                ListTile(
                  trailing: oversizedWidget,
                  title: Text('B'),
                  subtitle: Text('B'),
                  isThreeLine:  true,
                  dense: false,
                ),
              ],
            ),
          ),
        ),
      );

      expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 16.0 - 24.0,        16.0, 24.0, 56.0));
      expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 16.0 - 24.0, 88.0 + 16.0, 24.0, 56.0));
    });

    testWidgets('ListTile wide leading Widget', (WidgetTester tester) async {
      const Key leadingKey = ValueKey<String>('L');

      Widget buildFrame(double leadingWidth, TextDirection textDirection) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Directionality(
            textDirection: textDirection,
            child: Material(
              child: Container(
                alignment: Alignment.topLeft,
                child: ListTile(
                  contentPadding: EdgeInsets.zero,
                  leading: SizedBox(key: leadingKey, width: leadingWidth, height: 32.0),
                  title: const Text('title'),
                  subtitle: const Text('subtitle'),
                ),
              ),
            ),
          ),
        );
      }

      double left(String text) => tester.getTopLeft(find.text(text)).dx;
      double right(String text) => tester.getTopRight(find.text(text)).dx;

      // textDirection = LTR

      // Two-line tile's height = 72, leading 24x32 widget is positioned 16.0 pixels from the top.
      await tester.pumpWidget(buildFrame(24.0, TextDirection.ltr));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
      expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(0.0, 16.0));
      expect(tester.getBottomRight(find.byKey(leadingKey)), const Offset(24.0, 16.0 + 32.0));

      // Leading widget's width is 20, so default layout: the left edges of the
      // title and subtitle are at 56dps (contentPadding is zero).
      expect(left('title'), 56.0);
      expect(left('subtitle'), 56.0);

      // If the leading widget is wider than 40 it is separated from the
      // title and subtitle by 16.
      await tester.pumpWidget(buildFrame(56.0, TextDirection.ltr));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
      expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(0.0, 16.0));
      expect(tester.getBottomRight(find.byKey(leadingKey)), const Offset(56.0, 16.0 + 32.0));
      expect(left('title'), 72.0);
      expect(left('subtitle'), 72.0);

      // Same tests, textDirection = RTL

      await tester.pumpWidget(buildFrame(24.0, TextDirection.rtl));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
      expect(tester.getTopRight(find.byKey(leadingKey)), const Offset(800.0, 16.0));
      expect(tester.getBottomLeft(find.byKey(leadingKey)), const Offset(800.0 - 24.0, 16.0 + 32.0));
      expect(right('title'), 800.0 - 56.0);
      expect(right('subtitle'), 800.0 - 56.0);

      await tester.pumpWidget(buildFrame(56.0, TextDirection.rtl));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
      expect(tester.getTopRight(find.byKey(leadingKey)), const Offset(800.0, 16.0));
      expect(tester.getBottomLeft(find.byKey(leadingKey)), const Offset(800.0 - 56.0, 16.0 + 32.0));
      expect(right('title'), 800.0 - 72.0);
      expect(right('subtitle'), 800.0 - 72.0);
    });

    testWidgets('ListTile horizontalTitleGap = 0.0', (WidgetTester tester) async {
      Widget buildFrame(TextDirection textDirection, { double? themeHorizontalTitleGap, double? widgetHorizontalTitleGap }) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Directionality(
            textDirection: textDirection,
            child: Material(
              child: ListTileTheme(
                data: ListTileThemeData(horizontalTitleGap: themeHorizontalTitleGap),
                child: Container(
                  alignment: Alignment.topLeft,
                  child: ListTile(
                    horizontalTitleGap: widgetHorizontalTitleGap,
                    leading: const Text('L'),
                    title: const Text('title'),
                    trailing: const Text('T'),
                  ),
                ),
              ),
            ),
          ),
        );
      }

      double left(String text) => tester.getTopLeft(find.text(text)).dx;
      double right(String text) => tester.getTopRight(find.text(text)).dx;

      await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetHorizontalTitleGap: 0));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
      expect(left('title'), 56.0);

      await tester.pumpWidget(buildFrame(TextDirection.ltr, themeHorizontalTitleGap: 0));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
      expect(left('title'), 56.0);

      await tester.pumpWidget(buildFrame(TextDirection.ltr, themeHorizontalTitleGap: 10, widgetHorizontalTitleGap: 0));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
      expect(left('title'), 56.0);

      await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetHorizontalTitleGap: 0));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
      expect(right('title'), 744.0);

      await tester.pumpWidget(buildFrame(TextDirection.rtl, themeHorizontalTitleGap: 0));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
      expect(right('title'), 744.0);

      await tester.pumpWidget(buildFrame(TextDirection.rtl, themeHorizontalTitleGap: 10, widgetHorizontalTitleGap: 0));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
      expect(right('title'), 744.0);
    });

    testWidgets('ListTile horizontalTitleGap = (default) && ListTile minLeadingWidth = (default)', (WidgetTester tester) async {
      Widget buildFrame(TextDirection textDirection) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Directionality(
            textDirection: textDirection,
            child: Material(
              child: Container(
                alignment: Alignment.topLeft,
                child: const ListTile(
                  leading: Text('L'),
                  title: Text('title'),
                  trailing: Text('T'),
                ),
              ),
            ),
          ),
        );
      }

      double left(String text) => tester.getTopLeft(find.text(text)).dx;
      double right(String text) => tester.getTopRight(find.text(text)).dx;

      await tester.pumpWidget(buildFrame(TextDirection.ltr));

      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
      // horizontalTitleGap: ListTileDefaultValue.horizontalTitleGap (16.0)
      expect(left('title'), 72.0);

      await tester.pumpWidget(buildFrame(TextDirection.rtl));

      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
      // horizontalTitleGap: ListTileDefaultValue.horizontalTitleGap (16.0)
      expect(right('title'), 728.0);
    });

    testWidgets('ListTile horizontalTitleGap with visualDensity', (WidgetTester tester) async {
      Widget buildFrame({
        double? horizontalTitleGap,
        VisualDensity? visualDensity,
      }) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Directionality(
            textDirection: TextDirection.ltr,
            child: Material(
              child: Container(
                alignment: Alignment.topLeft,
                child: ListTile(
                  visualDensity: visualDensity,
                  horizontalTitleGap: horizontalTitleGap,
                  leading: const Text('L'),
                  title: const Text('title'),
                  trailing: const Text('T'),
                ),
              ),
            ),
          ),
        );
      }

      double left(String text) => tester.getTopLeft(find.text(text)).dx;

      await tester.pumpWidget(buildFrame(
        horizontalTitleGap: 10.0,
        visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity),
      ));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
      expect(left('title'), 58.0);

      // Pump another frame of the same widget to ensure the underlying render
      // object did not cache the original horizontalTitleGap calculation based on the
      // visualDensity
      await tester.pumpWidget(buildFrame(
        horizontalTitleGap: 10.0,
        visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity),
      ));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
      expect(left('title'), 58.0);
    });

    testWidgets('ListTile minVerticalPadding = 80.0', (WidgetTester tester) async {
      Widget buildFrame(TextDirection textDirection, { double? themeMinVerticalPadding, double? widgetMinVerticalPadding }) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Directionality(
            textDirection: textDirection,
            child: Material(
              child: ListTileTheme(
                data: ListTileThemeData(minVerticalPadding: themeMinVerticalPadding),
                child: Container(
                  alignment: Alignment.topLeft,
                  child: ListTile(
                    minVerticalPadding: widgetMinVerticalPadding,
                    leading: const Text('L'),
                    title: const Text('title'),
                    trailing: const Text('T'),
                  ),
                ),
              ),
            ),
          ),
        );
      }


      await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetMinVerticalPadding: 80));
      // 80 + 80 + 16(Title) = 176
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));

      await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinVerticalPadding: 80));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));

      await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinVerticalPadding: 0, widgetMinVerticalPadding: 80));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));

      await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetMinVerticalPadding: 80));
      // 80 + 80 + 16(Title) = 176
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));

      await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinVerticalPadding: 80));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));

      await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinVerticalPadding: 0, widgetMinVerticalPadding: 80));
      expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
    });

    testWidgets('ListTile font size', (WidgetTester tester) async {
      Widget buildFrame({
        bool dense = false,
        bool enabled = true,
        bool selected = false,
        ListTileStyle? style,
      }) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: Center(
              child: Builder(
                builder: (BuildContext context) {
                  return ListTile(
                    dense: dense,
                    enabled: enabled,
                    selected: selected,
                    style: style,
                    leading: const TestText('leading'),
                    title: const TestText('title'),
                    subtitle: const TestText('subtitle') ,
                    trailing: const TestText('trailing'),
                  );
                },
              ),
            ),
          ),
        );
      }

      // ListTile - ListTileStyle.list (default).
      await tester.pumpWidget(buildFrame());
      RenderParagraph leading = _getTextRenderObject(tester, 'leading');
      expect(leading.text.style!.fontSize, 14.0);
      RenderParagraph title = _getTextRenderObject(tester, 'title');
      expect(title.text.style!.fontSize, 16.0);
      RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
      expect(subtitle.text.style!.fontSize, 14.0);
      RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
      expect(trailing.text.style!.fontSize, 14.0);

      // ListTile - Densed - ListTileStyle.list (default).
      await tester.pumpWidget(buildFrame(dense: true));
      await tester.pumpAndSettle();
      leading = _getTextRenderObject(tester, 'leading');
      expect(leading.text.style!.fontSize, 14.0);
      title = _getTextRenderObject(tester, 'title');
      expect(title.text.style!.fontSize, 13.0);
      subtitle = _getTextRenderObject(tester, 'subtitle');
      expect(subtitle.text.style!.fontSize, 12.0);
      trailing = _getTextRenderObject(tester, 'trailing');
      expect(trailing.text.style!.fontSize, 14.0);

      // ListTile - ListTileStyle.drawer.
      await tester.pumpWidget(buildFrame(style: ListTileStyle.drawer));
      await tester.pumpAndSettle();
      leading = _getTextRenderObject(tester, 'leading');
      expect(leading.text.style!.fontSize, 14.0);
      title = _getTextRenderObject(tester, 'title');
      expect(title.text.style!.fontSize, 14.0);
      subtitle = _getTextRenderObject(tester, 'subtitle');
      expect(subtitle.text.style!.fontSize, 14.0);
      trailing = _getTextRenderObject(tester, 'trailing');
      expect(trailing.text.style!.fontSize, 14.0);

      // ListTile - Densed - ListTileStyle.drawer.
      await tester.pumpWidget(buildFrame(dense: true, style: ListTileStyle.drawer));
      await tester.pumpAndSettle();
      leading = _getTextRenderObject(tester, 'leading');
      expect(leading.text.style!.fontSize, 14.0);
      title = _getTextRenderObject(tester, 'title');
      expect(title.text.style!.fontSize, 13.0);
      subtitle = _getTextRenderObject(tester, 'subtitle');
      expect(subtitle.text.style!.fontSize, 12.0);
      trailing = _getTextRenderObject(tester, 'trailing');
      expect(trailing.text.style!.fontSize, 14.0);
    });

    testWidgets('ListTile text color', (WidgetTester tester) async {
      final ThemeData theme = ThemeData(useMaterial3: false);
      Widget buildFrame({
        bool dense = false,
        bool enabled = true,
        bool selected = false,
        ListTileStyle? style,
      }) {
        return MaterialApp(
          theme: theme,
          home: Material(
            child: Center(
              child: Builder(
                builder: (BuildContext context) {
                  return ListTile(
                    dense: dense,
                    enabled: enabled,
                    selected: selected,
                    style: style,
                    leading: const TestText('leading'),
                    title: const TestText('title'),
                    subtitle: const TestText('subtitle') ,
                    trailing: const TestText('trailing'),
                  );
                },
              ),
            ),
          ),
        );
      }

      // ListTile - ListTileStyle.list (default).
      await tester.pumpWidget(buildFrame());
      RenderParagraph leading = _getTextRenderObject(tester, 'leading');
      expect(leading.text.style!.color, theme.textTheme.bodyMedium!.color);
      RenderParagraph title = _getTextRenderObject(tester, 'title');
      expect(title.text.style!.color, theme.textTheme.titleMedium!.color);
      RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
      expect(subtitle.text.style!.color, theme.textTheme.bodySmall!.color);
      RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
      expect(trailing.text.style!.color, theme.textTheme.bodyMedium!.color);

      // ListTile - ListTileStyle.drawer.
      await tester.pumpWidget(buildFrame(style: ListTileStyle.drawer));
      await tester.pumpAndSettle();
      leading = _getTextRenderObject(tester, 'leading');
      expect(leading.text.style!.color, theme.textTheme.bodyMedium!.color);
      title = _getTextRenderObject(tester, 'title');
      expect(title.text.style!.color, theme.textTheme.titleMedium!.color);
      subtitle = _getTextRenderObject(tester, 'subtitle');
      expect(subtitle.text.style!.color, theme.textTheme.bodySmall!.color);
      trailing = _getTextRenderObject(tester, 'trailing');
      expect(trailing.text.style!.color, theme.textTheme.bodyMedium!.color);
    });

    testWidgets('selected, enabled ListTile default icon color, light and dark themes', (WidgetTester tester) async {
      // Regression test for https://github.com/flutter/flutter/pull/77004

      const ColorScheme lightColorScheme = ColorScheme.light();
      const ColorScheme darkColorScheme = ColorScheme.dark();
      final Key leadingKey = UniqueKey();
      final Key titleKey = UniqueKey();
      final Key subtitleKey = UniqueKey();
      final Key trailingKey = UniqueKey();

      Widget buildFrame({ required Brightness brightness, required bool selected }) {
        final ThemeData theme = brightness == Brightness.light
          ? ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: false)
          : ThemeData.from(colorScheme: const ColorScheme.dark(), useMaterial3: false);
        return MaterialApp(
          theme: theme,
          home: Material(
            child: Center(
              child: ListTile(
                selected: selected,
                leading: TestIcon(key: leadingKey),
                title: TestIcon(key: titleKey),
                subtitle: TestIcon(key: subtitleKey),
                trailing: TestIcon(key: trailingKey),
              ),
            ),
          ),
        );
      }

      Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;

      await tester.pumpWidget(buildFrame(brightness: Brightness.light, selected: true));
      expect(iconColor(leadingKey), lightColorScheme.primary);
      expect(iconColor(titleKey), lightColorScheme.primary);
      expect(iconColor(subtitleKey), lightColorScheme.primary);
      expect(iconColor(trailingKey), lightColorScheme.primary);

      await tester.pumpWidget(buildFrame(brightness: Brightness.light, selected: false));
      expect(iconColor(leadingKey), Colors.black45);
      expect(iconColor(titleKey), Colors.black45);
      expect(iconColor(subtitleKey), Colors.black45);
      expect(iconColor(trailingKey), Colors.black45);

      await tester.pumpWidget(buildFrame(brightness: Brightness.dark, selected: true));
      await tester.pumpAndSettle(); // Animated theme change
      expect(iconColor(leadingKey), darkColorScheme.primary);
      expect(iconColor(titleKey), darkColorScheme.primary);
      expect(iconColor(subtitleKey), darkColorScheme.primary);
      expect(iconColor(trailingKey), darkColorScheme.primary);

      // For this configuration, ListTile defers to the default IconTheme.
      // The default dark theme's IconTheme has color:white
      await tester.pumpWidget(buildFrame(brightness: Brightness.dark, selected: false));
      expect(iconColor(leadingKey),  Colors.white);
      expect(iconColor(titleKey),  Colors.white);
      expect(iconColor(subtitleKey),  Colors.white);
      expect(iconColor(trailingKey), Colors.white);
    });

    testWidgets('ListTile default tile color', (WidgetTester tester) async {
      bool isSelected = false;
      const Color defaultColor = Colors.transparent;

      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: Center(
              child: StatefulBuilder(
                builder: (BuildContext context, StateSetter setState) {
                  return ListTile(
                    selected: isSelected,
                    onTap: () {
                      setState(()=> isSelected = !isSelected);
                    },
                    title: const Text('Title'),
                  );
                },
              ),
            ),
          ),
        ),
      );

      expect(find.byType(Material), paints..rect(color: defaultColor));

      // Tap on tile to change isSelected.
      await tester.tap(find.byType(ListTile));
      await tester.pumpAndSettle();

      expect(find.byType(Material), paints..rect(color: defaultColor));
    });

    testWidgets('titleAlignment position with title widget', (WidgetTester tester) async {
      final Key leadingKey = GlobalKey();
      final Key trailingKey = GlobalKey();
      const double leadingHeight = 24.0;
      const double titleHeight = 50.0;
      const double trailingHeight = 24.0;
      const double minVerticalPadding = 10.0;
      const double tileHeight = minVerticalPadding * 2 + titleHeight;

      Widget buildFrame({ ListTileTitleAlignment? titleAlignment }) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: Center(
              child: ListTile(
                titleAlignment: titleAlignment,
                minVerticalPadding: minVerticalPadding,
                leading: SizedBox(key: leadingKey, width: 24.0, height: leadingHeight),
                title: const SizedBox(width: 20.0, height: titleHeight),
                trailing: SizedBox(key: trailingKey, width: 24.0, height: trailingHeight),
              ),
            ),
          ),
        );
      }

      // If [ThemeData.useMaterial3] is false, the default title alignment is
      // [ListTileTitleAlignment.titleHeight], If the tile height is less than
      // 72.0 pixels, the leading is placed 16.0 pixels below the top of
      // the title widget and the trailing is centered vertically in the tile.
      await tester.pumpWidget(buildFrame());
      Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
      Offset leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      Offset trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are centered vertically in the tile.
      const double titlePosition = 16.0;
      const double centerPosition = (tileHeight / 2) - (leadingHeight / 2);
      expect(leadingOffset.dy - tileOffset.dy, titlePosition);
      expect(trailingOffset.dy - tileOffset.dy, centerPosition);

      // Test [ListTileTitleAlignment.threeLine] alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.threeLine));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are centered vertically in the tile,
      // If the [ListTile.isThreeLine] property is false.
      expect(leadingOffset.dy - tileOffset.dy, centerPosition);
      expect(trailingOffset.dy - tileOffset.dy, centerPosition);

      // Test [ListTileTitleAlignment.titleHeight] alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.titleHeight));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // If the tile height is less than 72.0 pixels, the leading is placed
      // 16.0 pixels below the top of the tile widget, and the trailing is
      // centered vertically in the tile.
      expect(leadingOffset.dy - tileOffset.dy, titlePosition);
      expect(trailingOffset.dy - tileOffset.dy, centerPosition);

      // Test [ListTileTitleAlignment.top] alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.top));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are placed minVerticalPadding below
      // the top of the title widget.
      const double topPosition = minVerticalPadding;
      expect(leadingOffset.dy - tileOffset.dy, topPosition);
      expect(trailingOffset.dy - tileOffset.dy, topPosition);

      // Test [ListTileTitleAlignment.center] alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.center));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are vertically centered in the tile.
      expect(leadingOffset.dy - tileOffset.dy, centerPosition);
      expect(trailingOffset.dy - tileOffset.dy, centerPosition);

      // Test [ListTileTitleAlignment.bottom] alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.bottom));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are placed minVerticalPadding above
      // the bottom of the subtitle widget.
      const double bottomPosition = tileHeight - minVerticalPadding - leadingHeight;
      expect(leadingOffset.dy - tileOffset.dy, bottomPosition);
      expect(trailingOffset.dy - tileOffset.dy, bottomPosition);
    });

    testWidgets('titleAlignment position with title and subtitle widgets', (WidgetTester tester) async {
      final Key leadingKey = GlobalKey();
      final Key trailingKey = GlobalKey();
      const double leadingHeight = 24.0;
      const double titleHeight = 50.0;
      const double subtitleHeight = 50.0;
      const double trailingHeight = 24.0;
      const double minVerticalPadding = 10.0;
      const double tileHeight = minVerticalPadding * 2 + titleHeight + subtitleHeight;

      Widget buildFrame({ ListTileTitleAlignment? titleAlignment }) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: Center(
              child: ListTile(
                titleAlignment: titleAlignment,
                minVerticalPadding: minVerticalPadding,
                leading: SizedBox(key: leadingKey, width: 24.0, height: leadingHeight),
                title: const SizedBox(width: 20.0, height: titleHeight),
                subtitle: const SizedBox(width: 20.0, height: subtitleHeight),
                trailing: SizedBox(key: trailingKey, width: 24.0, height: trailingHeight),
              ),
            ),
          ),
        );
      }

      // If [ThemeData.useMaterial3] is false, the default title alignment is
      // [ListTileTitleAlignment.titleHeight], which positions the leading and
      // trailing widgets 16.0 pixels below the top of the tile widget.
      await tester.pumpWidget(buildFrame());
      Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
      Offset leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      Offset trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are positioned 16.0 pixels below the
      // top of the tile widget.
      const double titlePosition = 16.0;
      expect(leadingOffset.dy - tileOffset.dy, titlePosition);
      expect(trailingOffset.dy - tileOffset.dy, titlePosition);

      // Test [ListTileTitleAlignment.threeLine] alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.threeLine));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are vertically centered in the tile,
      // If the [ListTile.isThreeLine] property is false.
      const double centerPosition = (tileHeight / 2) - (leadingHeight / 2);
      expect(leadingOffset.dy - tileOffset.dy, centerPosition);
      expect(trailingOffset.dy - tileOffset.dy, centerPosition);

      // Test [ListTileTitleAlignment.titleHeight] alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.titleHeight));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are positioned 16.0 pixels below the
      // top of the tile widget.
      expect(leadingOffset.dy - tileOffset.dy, titlePosition);
      expect(trailingOffset.dy - tileOffset.dy, titlePosition);

      // Test [ListTileTitleAlignment.top] alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.top));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are placed minVerticalPadding below
      // the top of the tile widget.
      const double topPosition = minVerticalPadding;
      expect(leadingOffset.dy - tileOffset.dy, topPosition);
      expect(trailingOffset.dy - tileOffset.dy, topPosition);

      // Test [ListTileTitleAlignment.center] alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.center));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are vertically centered in the tile.
      expect(leadingOffset.dy - tileOffset.dy, centerPosition);
      expect(trailingOffset.dy - tileOffset.dy, centerPosition);

      // Test [ListTileTitleAlignment.bottom] alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.bottom));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // Leading and trailing widgets are placed minVerticalPadding above
      // the bottom of the subtitle widget.
      const double bottomPosition = tileHeight - minVerticalPadding - leadingHeight;
      expect(leadingOffset.dy - tileOffset.dy, bottomPosition);
      expect(trailingOffset.dy - tileOffset.dy, bottomPosition);
    });

    testWidgets("ListTile.isThreeLine updates ListTileTitleAlignment.threeLine's alignment", (WidgetTester tester) async {
      final Key leadingKey = GlobalKey();
      final Key trailingKey = GlobalKey();
      const double leadingHeight = 24.0;
      const double titleHeight = 50.0;
      const double subtitleHeight = 50.0;
      const double trailingHeight = 24.0;
      const double minVerticalPadding = 10.0;
      const double tileHeight = minVerticalPadding * 2 + titleHeight + subtitleHeight;

      Widget buildFrame({ ListTileTitleAlignment? titleAlignment, bool isThreeLine = false }) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Material(
            child: Center(
              child: ListTile(
                titleAlignment: titleAlignment,
                minVerticalPadding: minVerticalPadding,
                leading: SizedBox(key: leadingKey, width: 24.0, height: leadingHeight),
                title: const SizedBox(width: 20.0, height: titleHeight),
                subtitle: const SizedBox(width: 20.0, height: subtitleHeight),
                trailing: SizedBox(key: trailingKey, width: 24.0, height: trailingHeight),
                isThreeLine: isThreeLine,
              ),
            ),
          ),
        );
      }

      // Set title alignment to threeLine.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.threeLine));
      Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
      Offset leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      Offset trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // If title alignment is threeLine and [ListTile.isThreeLine] is false,
      // leading and trailing widgets are centered vertically in the tile.
      const double leadingTrailingPosition = (tileHeight / 2) - (leadingHeight / 2);
      expect(leadingOffset.dy - tileOffset.dy, leadingTrailingPosition);
      expect(trailingOffset.dy - tileOffset.dy, leadingTrailingPosition);

      // Set [ListTile.isThreeLine] to true to update the alignment.
      await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.threeLine, isThreeLine: true));
      tileOffset = tester.getTopLeft(find.byType(ListTile));
      leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
      trailingOffset = tester.getTopRight(find.byKey(trailingKey));

      // The leading and trailing widgets are placed minVerticalPadding
      // to the top of the tile widget.
      expect(leadingOffset.dy - tileOffset.dy, minVerticalPadding);
      expect(trailingOffset.dy - tileOffset.dy, minVerticalPadding);
    });
  });
}

RenderParagraph _getTextRenderObject(WidgetTester tester, String text) {
  return tester.renderObject(find.descendant(
    of: find.byType(ListTile),
    matching: find.text(text),
  ));
}