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

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

Widget wrap({ required Widget child }) {
  return MediaQuery(
    data: const MediaQueryData(),
    child: Directionality(
      textDirection: TextDirection.ltr,
      child: Material(child: child),
    ),
  );
}

void main() {
  testWidgets('SwitchListTile control test', (WidgetTester tester) async {
    final List<dynamic> log = <dynamic>[];
    await tester.pumpWidget(wrap(
      child: SwitchListTile(
        value: true,
        onChanged: (bool value) { log.add(value); },
        title: const Text('Hello'),
      ),
    ));
    await tester.tap(find.text('Hello'));
    log.add('-');
    await tester.tap(find.byType(Switch));
    expect(log, equals(<dynamic>[false, '-', false]));
  });

  testWidgets('SwitchListTile semantics test', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);
    await tester.pumpWidget(wrap(
      child: Column(
        children: <Widget>[
          SwitchListTile(
            value: true,
            onChanged: (bool value) { },
            title: const Text('AAA'),
            secondary: const Text('aaa'),
          ),
          CheckboxListTile(
            value: true,
            onChanged: (bool? value) { },
            title: const Text('BBB'),
            secondary: const Text('bbb'),
          ),
          RadioListTile<bool>(
            value: true,
            groupValue: false,
            onChanged: (bool? value) { },
            title: const Text('CCC'),
            secondary: const Text('ccc'),
          ),
        ],
      ),
    ));

    // This test verifies that the label and the control get merged.
    expect(semantics, hasSemantics(TestSemantics.root(
      children: <TestSemantics>[
        TestSemantics.rootChild(
          id: 1,
          rect: const Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
          flags: <SemanticsFlag>[
            SemanticsFlag.hasEnabledState,
            SemanticsFlag.hasToggledState,
            SemanticsFlag.isEnabled,
            SemanticsFlag.isFocusable,
            SemanticsFlag.isToggled,
          ],
          actions: SemanticsAction.tap.index,
          label: 'aaa\nAAA',
        ),
        TestSemantics.rootChild(
          id: 3,
          rect: const Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
          transform: Matrix4.translationValues(0.0, 56.0, 0.0),
          flags: <SemanticsFlag>[
            SemanticsFlag.hasCheckedState,
            SemanticsFlag.hasEnabledState,
            SemanticsFlag.isChecked,
            SemanticsFlag.isEnabled,
            SemanticsFlag.isFocusable,
          ],
          actions: SemanticsAction.tap.index,
          label: 'bbb\nBBB',
        ),
        TestSemantics.rootChild(
          id: 5,
          rect: const Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
          transform: Matrix4.translationValues(0.0, 112.0, 0.0),
          flags: <SemanticsFlag>[
            SemanticsFlag.hasCheckedState,
            SemanticsFlag.hasEnabledState,
            SemanticsFlag.isEnabled,
            SemanticsFlag.isFocusable,
            SemanticsFlag.isInMutuallyExclusiveGroup,
          ],
          actions: SemanticsAction.tap.index,
          label: 'CCC\nccc',
        ),
      ],
    )));

    semantics.dispose();
  });

  testWidgets('SwitchListTile has the right colors', (WidgetTester tester) async {
    bool value = false;
    await tester.pumpWidget(
      MediaQuery(
        data: const MediaQueryData(padding: EdgeInsets.all(8.0)),
        child: Directionality(
        textDirection: TextDirection.ltr,
        child:
          StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: SwitchListTile(
                  value: value,
                  onChanged: (bool newValue) {
                    setState(() { value = newValue; });
                  },
                  activeColor: Colors.red[500],
                  activeTrackColor: Colors.green[500],
                  inactiveThumbColor: Colors.yellow[500],
                  inactiveTrackColor: Colors.blue[500],
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(
      find.byType(Switch),
      paints
        ..rrect(color: Colors.blue[500])
        ..rrect(color: const Color(0x33000000))
        ..rrect(color: const Color(0x24000000))
        ..rrect(color: const Color(0x1f000000))
        ..rrect(color: Colors.yellow[500]),
    );

    await tester.tap(find.byType(Switch));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect(color: Colors.green[500])
        ..rrect(color: const Color(0x33000000))
        ..rrect(color: const Color(0x24000000))
        ..rrect(color: const Color(0x1f000000))
        ..rrect(color: Colors.red[500]),
    );
  });

  testWidgets('SwitchListTile.adaptive delegates to', (WidgetTester tester) async {
    bool value = false;

    Widget buildFrame(TargetPlatform platform) {
      return MaterialApp(
        theme: ThemeData(platform: platform),
        home: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Material(
              child: Center(
                child: SwitchListTile.adaptive(
                  value: value,
                  onChanged: (bool newValue) {
                    setState(() {
                      value = newValue;
                    });
                  },
                ),
              ),
            );
          },
        ),
      );
    }

    for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.macOS ]) {
      value = false;
      await tester.pumpWidget(buildFrame(platform));
      expect(find.byType(CupertinoSwitch), findsOneWidget);
      expect(value, isFalse, reason: 'on ${platform.name}');

      await tester.tap(find.byType(SwitchListTile));
      expect(value, isTrue, reason: 'on ${platform.name}');
    }

    for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows ]) {
      value = false;
      await tester.pumpWidget(buildFrame(platform));
      await tester.pumpAndSettle(); // Finish the theme change animation.

      expect(find.byType(CupertinoSwitch), findsNothing);
      expect(value, isFalse, reason: 'on ${platform.name}');
      await tester.tap(find.byType(SwitchListTile));
      expect(value, isTrue, reason: 'on ${platform.name}');
    }
  });

  testWidgets('SwitchListTile 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: SwitchListTile(
                contentPadding: const EdgeInsetsDirectional.only(
                  start: 10.0,
                  end: 20.0,
                  top: 30.0,
                  bottom: 40.0,
                ),
                secondary: const Text('L'),
                title: const Text('title'),
                value: true,
                onChanged: (bool selected) {},
              ),
            ),
          ),
        ),
      );
    }

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

    expect(tester.getTopLeft(find.text('L')).dx, 10.0); // contentPadding.start = 10
    expect(tester.getTopRight(find.byType(Switch)).dx, 780.0); // 800 - contentPadding.end

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

    expect(tester.getTopLeft(find.byType(Switch)).dx, 20.0); // contentPadding.end = 20
    expect(tester.getTopRight(find.text('L')).dx, 790.0); // 800 - contentPadding.start
  });

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

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

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

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: ListView(
            children: <Widget>[
              SwitchListTile(
                value: true,
                onChanged: null,
                title: Text('A', key: childKey),
                autofocus: true,
              ),
            ],
          ),
        ),
      ),
    );

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

  testWidgets('SwitchListTile controlAffinity test', (WidgetTester tester) async {
    await tester.pumpWidget(const MaterialApp(
      home: Material(
        child: SwitchListTile(
          value: true,
          onChanged: null,
          secondary: Icon(Icons.info),
          title: Text('Title'),
          controlAffinity: ListTileControlAffinity.leading,
        ),
      ),
    ));

    final ListTile listTile = tester.widget(find.byType(ListTile));
    // When controlAffinity is ListTileControlAffinity.leading, the position of
    // Switch is at leading edge and SwitchListTile.secondary at trailing edge.
    expect(listTile.leading.runtimeType, Switch);
    expect(listTile.trailing.runtimeType, Icon);
  });

  testWidgets('SwitchListTile controlAffinity default value test', (WidgetTester tester) async {
    await tester.pumpWidget(const MaterialApp(
      home: Material(
        child: SwitchListTile(
          value: true,
          onChanged: null,
          secondary: Icon(Icons.info),
          title: Text('Title'),
        ),
      ),
    ));

    final ListTile listTile = tester.widget(find.byType(ListTile));
    // By default, value of controlAffinity is ListTileControlAffinity.platform,
    // where the position of SwitchListTile.secondary is at leading edge and Switch
    // at trailing edge. This also covers test for ListTileControlAffinity.trailing.
    expect(listTile.leading.runtimeType, Icon);
    expect(listTile.trailing.runtimeType, Switch);
  });

  testWidgets('SwitchListTile respects shape', (WidgetTester tester) async {
    const ShapeBorder shapeBorder = RoundedRectangleBorder(
      borderRadius: BorderRadius.horizontal(right: Radius.circular(100)),
    );

    await tester.pumpWidget(const MaterialApp(
      home: Material(
        child: SwitchListTile(
          value: true,
          onChanged: null,
          title: Text('Title'),
          shape: shapeBorder,
        ),
      ),
    ));

    expect(tester.widget<InkWell>(find.byType(InkWell)).customBorder, shapeBorder);
  });

  testWidgets('SwitchListTile respects tileColor', (WidgetTester tester) async {
    final Color tileColor = Colors.red.shade500;

    await tester.pumpWidget(
      wrap(
        child: Center(
          child: SwitchListTile(
            value: false,
            onChanged: null,
            title: const Text('Title'),
            tileColor: tileColor,
          ),
        ),
      ),
    );

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

  testWidgets('SwitchListTile respects selectedTileColor', (WidgetTester tester) async {
    final Color selectedTileColor = Colors.green.shade500;

    await tester.pumpWidget(
      wrap(
        child: Center(
          child: SwitchListTile(
            value: false,
            onChanged: null,
            title: const Text('Title'),
            selected: true,
            selectedTileColor: selectedTileColor,
          ),
        ),
      ),
    );

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

  testWidgets('SwitchListTile selected item text Color', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/pull/76909

    const Color activeColor = Color(0xff00ff00);

    Widget buildFrame({ Color? activeColor, Color? thumbColor }) {
      return MaterialApp(
        theme: ThemeData.light().copyWith(
          switchTheme: SwitchThemeData(
            thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
              return states.contains(MaterialState.selected) ? thumbColor : null;
            }),
          ),
        ),
        home: Scaffold(
          body: Center(
            child: SwitchListTile(
              activeColor: activeColor,
              selected: true,
              title: const Text('title'),
              value: true,
              onChanged: (bool? value) { },
            ),
          ),
        ),
      );
    }

    Color? textColor(String text) {
      return tester.renderObject<RenderParagraph>(find.text(text)).text.style?.color;
    }

    await tester.pumpWidget(buildFrame(activeColor: activeColor));
    expect(textColor('title'), activeColor);

    await tester.pumpWidget(buildFrame(activeColor: activeColor));
    expect(textColor('title'), activeColor);
  });

  testWidgets('SwitchListTile respects visualDensity', (WidgetTester tester) async {
    const Key key = Key('test');
    Future<void> buildTest(VisualDensity visualDensity) async {
      return tester.pumpWidget(
        wrap(
          child: Center(
            child: SwitchListTile(
              key: key,
              value: false,
              onChanged: (bool? value) {},
              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)));
  });

  testWidgets('SwitchListTile respects focusNode', (WidgetTester tester) async {
    final GlobalKey childKey = GlobalKey();
    await tester.pumpWidget(
      wrap(
        child: Center(
          child: SwitchListTile(
            value: false,
            title: Text('A', key: childKey),
            onChanged: (bool? value) {},
          ),
        ),
      ),
    );

    await tester.pump();
    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);
  });

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

    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('SwitchListTile.adaptive onFocusChange Callback', (WidgetTester tester) async {
    final FocusNode node = FocusNode(debugLabel: 'SwitchListTile.adaptive onFocusChange');
    bool gotFocus = false;
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: SwitchListTile.adaptive(
            value: true,
            focusNode: node,
            onFocusChange: (bool focused) {
              gotFocus = focused;
            },
            onChanged: (bool value) {},
          ),
        ),
      ),
    );

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

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

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

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

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

    testWidgets('SwitchListTile respects enableFeedback', (WidgetTester tester) async {
      Future<void> buildTest(bool enableFeedback) async {
        return tester.pumpWidget(
          wrap(
            child: Center(
              child: SwitchListTile(
                value: false,
                onChanged: (bool? value) {},
                enableFeedback: enableFeedback,
              ),
            ),
          ),
        );
      }

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

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

  testWidgets('SwitchListTile respects hoverColor', (WidgetTester tester) async {
    const Key key = Key('test');
    await tester.pumpWidget(
      wrap(
        child: Center(
          child: StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
            return Container(
              width: 100,
              height: 100,
              color: Colors.white,
              child: SwitchListTile(
                value: false,
                key: key,
                hoverColor: Colors.orange[500],
                title: const Text('A'),
                onChanged: (bool? value) {},
              ),
            );
          }),
        ),
      ),
    );

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

    await tester.pump();
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byKey(key))),
      paints..rect()..rect(
        color: Colors.orange[500],
        rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
      )
    );
  });

  testWidgets('SwitchListTile respects thumbColor in active/enabled states', (WidgetTester tester) async {
    const Color activeEnabledThumbColor = Color(0xFF000001);
    const Color activeDisabledThumbColor = Color(0xFF000002);
    const Color inactiveEnabledThumbColor = Color(0xFF000003);
    const Color inactiveDisabledThumbColor = Color(0xFF000004);

    Color getThumbColor(Set<MaterialState> states) {
      if (states.contains(MaterialState.disabled)) {
        if (states.contains(MaterialState.selected)) {
          return activeDisabledThumbColor;
        }
        return inactiveDisabledThumbColor;
      }
      if (states.contains(MaterialState.selected)) {
        return activeEnabledThumbColor;
      }
      return inactiveEnabledThumbColor;
    }

    final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor);

    Widget buildSwitchListTile({required bool enabled, required bool selected}) {
      return wrap(
        child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return SwitchListTile(
                value: selected,
                thumbColor: thumbColor,
                onChanged: enabled ? (_) { } : null,
              );
            }),
      );
    }

    await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: false));
    await tester.pumpAndSettle();
    expect(
        Material.of(tester.element(find.byType(Switch))),
        paints
          ..rrect()..rrect()..rrect()..rrect()
          ..rrect(color: inactiveDisabledThumbColor),
    );

    await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: true));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()..rrect()..rrect()..rrect()
        ..rrect(color: activeDisabledThumbColor),
    );

    await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: false));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()..rrect()..rrect()..rrect()
        ..rrect(color: inactiveEnabledThumbColor),
    );

    await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: true));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()..rrect()..rrect()..rrect()
        ..rrect(color: activeEnabledThumbColor),
    );
  });

  testWidgets('SwitchListTile respects thumbColor in hovered/pressed states', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    const Color hoveredThumbColor = Color(0xFF4caf50);
    const Color pressedThumbColor = Color(0xFFF44336);

    Color getThumbColor(Set<MaterialState> states) {
      if (states.contains(MaterialState.pressed)) {
        return pressedThumbColor;
      }
      if (states.contains(MaterialState.hovered)) {
        return hoveredThumbColor;
      }
      return Colors.transparent;
    }

    final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor);

    Widget buildSwitchListTile() {
      return MaterialApp(
        theme: ThemeData(),
        home: wrap(
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return SwitchListTile(
                value: false,
                thumbColor: thumbColor,
                onChanged: (_) { },
              );
            }),
        ),
      );
    }

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

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

    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()..rrect()..rrect()..rrect()
        ..rrect(color: hoveredThumbColor),
    );

    // On pressed state
    await tester.press(find.byType(Switch));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()..rrect()..rrect()..rrect()
        ..rrect(color: pressedThumbColor),
    );
  });

  testWidgets('SwitchListTile respects trackColor in active/enabled states', (WidgetTester tester) async {
    const Color activeEnabledTrackColor = Color(0xFF000001);
    const Color activeDisabledTrackColor = Color(0xFF000002);
    const Color inactiveEnabledTrackColor = Color(0xFF000003);
    const Color inactiveDisabledTrackColor = Color(0xFF000004);

    Color getTrackColor(Set<MaterialState> states) {
      if (states.contains(MaterialState.disabled)) {
        if (states.contains(MaterialState.selected)) {
          return activeDisabledTrackColor;
        }
        return inactiveDisabledTrackColor;
      }
      if (states.contains(MaterialState.selected)) {
        return activeEnabledTrackColor;
      }
      return inactiveEnabledTrackColor;
    }

    final MaterialStateProperty<Color> trackColor = MaterialStateColor.resolveWith(getTrackColor);

    Widget buildSwitchListTile({required bool enabled, required bool selected}) {
      return wrap(
        child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return SwitchListTile(
                value: selected,
                trackColor: trackColor,
                onChanged: enabled ? (_) { } : null,
              );
            }),
      );
    }

    await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: false));

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..rrect(color: inactiveDisabledTrackColor),
    );

    await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: true));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..rrect(color: activeDisabledTrackColor),
    );

    await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: false));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..rrect(color: inactiveEnabledTrackColor),
    );

    await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: true));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..rrect(color: activeEnabledTrackColor),
    );
  });

  testWidgets('SwitchListTile respects trackColor in hovered states', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    const Color hoveredTrackColor = Color(0xFF4caf50);

    Color getTrackColor(Set<MaterialState> states) {
      if (states.contains(MaterialState.hovered)) {
        return hoveredTrackColor;
      }
      return Colors.transparent;
    }

    final MaterialStateProperty<Color> trackColor = MaterialStateColor.resolveWith(getTrackColor);

    Widget buildSwitchListTile() {
      return wrap(
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return SwitchListTile(
              value: false,
              trackColor: trackColor,
              onChanged: (_) { },
            );
          }),
      );
    }

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

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

    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..rrect(color: hoveredTrackColor),
    );
  });

  testWidgets('SwitchListTile respects thumbIcon - M3', (WidgetTester tester) async {
    const Icon activeIcon = Icon(Icons.check);
    const Icon inactiveIcon = Icon(Icons.close);

    MaterialStateProperty<Icon?> thumbIcon(Icon? activeIcon, Icon? inactiveIcon) {
      return MaterialStateProperty.resolveWith<Icon?>((Set<MaterialState> states) {
        if (states.contains(MaterialState.selected)) {
          return activeIcon;
        }
        return inactiveIcon;
      });
    }

    Widget buildSwitchListTile({required bool enabled, required bool active, Icon? activeIcon, Icon? inactiveIcon}) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: wrap(
          child: StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                return SwitchListTile(
                  thumbIcon: thumbIcon(activeIcon, inactiveIcon),
                  value: active,
                  onChanged: enabled ? (_) {} : null,
                );
              }),
        ),
      );
    }

    // active icon shows when switch is on.
    await tester.pumpWidget(buildSwitchListTile(enabled: true, active: true, activeIcon: activeIcon));
    await tester.pumpAndSettle();
    final Switch switchWidget0 = tester.widget<Switch>(find.byType(Switch));
    expect(switchWidget0.thumbIcon?.resolve(<MaterialState>{MaterialState.selected}), activeIcon);
    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()..rrect()
        ..paragraph(offset: const Offset(32.0, 12.0)),
    );

    // inactive icon shows when switch is off.
    await tester.pumpWidget(buildSwitchListTile(enabled: true, active: false, inactiveIcon: inactiveIcon));
    await tester.pumpAndSettle();
    final Switch switchWidget1 = tester.widget<Switch>(find.byType(Switch));
    expect(switchWidget1.thumbIcon?.resolve(<MaterialState>{}), inactiveIcon);
    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()..rrect()
        ..rrect()
        ..paragraph(offset: const Offset(12.0, 12.0)),
    );

    // active icon doesn't show when switch is off.
    await tester.pumpWidget(buildSwitchListTile(enabled: true, active: false, activeIcon: activeIcon));
    await tester.pumpAndSettle();
    final Switch switchWidget2 = tester.widget<Switch>(find.byType(Switch));
    expect(switchWidget2.thumbIcon?.resolve(<MaterialState>{MaterialState.selected}), activeIcon);
    expect(
        Material.of(tester.element(find.byType(Switch))),
        paints
          ..rrect()..rrect()..rrect()
    );

    // inactive icon doesn't show when switch is on.
    await tester.pumpWidget(buildSwitchListTile(enabled: true, active: true, inactiveIcon: inactiveIcon));
    await tester.pumpAndSettle();
    final Switch switchWidget3 = tester.widget<Switch>(find.byType(Switch));
    expect(switchWidget3.thumbIcon?.resolve(<MaterialState>{}), inactiveIcon);
    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()..rrect()..restore(),
    );

    // without icon
    await tester.pumpWidget(buildSwitchListTile(enabled: true, active: false));
    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()..rrect()..rrect()..restore(),
    );
  });

  testWidgets('SwitchListTile respects materialTapTargetSize', (WidgetTester tester) async {
    Widget buildSwitchListTile(MaterialTapTargetSize materialTapTargetSize) {
      return wrap(
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return SwitchListTile(
              materialTapTargetSize: materialTapTargetSize,
              value: false,
              onChanged: (_) {},
            );
          }),
      );
    }

    await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.padded));
    final Switch switchWidget = tester.widget<Switch>(find.byType(Switch));
    expect(switchWidget.materialTapTargetSize, MaterialTapTargetSize.padded);
    expect(tester.getSize(find.byType(Switch)), const Size(59.0, 48.0));

    await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.shrinkWrap));
    final Switch switchWidget1 = tester.widget<Switch>(find.byType(Switch));
    expect(switchWidget1.materialTapTargetSize, MaterialTapTargetSize.shrinkWrap);
    expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0));
  });

  testWidgets('SwitchListTile.adaptive respects applyCupertinoTheme', (WidgetTester tester) async {
    final ThemeData theme = ThemeData();
    Widget buildSwitchListTile(bool applyCupertinoTheme, TargetPlatform platform) {
      return MaterialApp(
        theme: theme.copyWith(platform: platform),
        home: wrap(
          child: StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                return SwitchListTile.adaptive(
                  applyCupertinoTheme: applyCupertinoTheme,
                  value: true,
                  onChanged: (_) {},
                );
              }),
        ),
      );
    }

    for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.macOS ]) {
      await tester.pumpWidget(buildSwitchListTile(true, platform));
      await tester.pumpAndSettle();
      expect(find.byType(CupertinoSwitch), findsOneWidget);
      expect(
        Material.of(tester.element(find.byType(Switch))),
        paints..rrect(color: theme.useMaterial3 ? const Color(0xFF6750A4) : const Color(0xFF2196F3)),
      );

      await tester.pumpWidget(buildSwitchListTile(false, platform));
      await tester.pumpAndSettle();
      expect(find.byType(CupertinoSwitch), findsOneWidget);
      expect(
        Material.of(tester.element(find.byType(Switch))),
        paints..rrect(color: const Color(0xFF34C759)),
      );
    }
  });

  testWidgets('SwitchListTile respects materialTapTargetSize', (WidgetTester tester) async {
    Widget buildSwitchListTile(MaterialTapTargetSize materialTapTargetSize) {
      return wrap(
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return SwitchListTile(
              materialTapTargetSize: materialTapTargetSize,
              value: false,
              onChanged: (_) {},
            );
          }),
      );
    }

    await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.padded));
    final Switch switchWidget = tester.widget<Switch>(find.byType(Switch));
    expect(switchWidget.materialTapTargetSize, MaterialTapTargetSize.padded);
    expect(tester.getSize(find.byType(Switch)), const Size(59.0, 48.0));

    await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.shrinkWrap));
    final Switch switchWidget1 = tester.widget<Switch>(find.byType(Switch));
    expect(switchWidget1.materialTapTargetSize, MaterialTapTargetSize.shrinkWrap);
    expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0));
  });

  testWidgets('SwitchListTile passes the value of dragStartBehavior to Switch', (WidgetTester tester) async {
    Widget buildSwitchListTile(DragStartBehavior dragStartBehavior) {
      return wrap(
        child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return SwitchListTile(
                dragStartBehavior: dragStartBehavior,
                value: false,
                onChanged: (_) {},
              );
            }),
      );
    }

    await tester.pumpWidget(buildSwitchListTile(DragStartBehavior.start));
    final Switch switchWidget = tester.widget<Switch>(find.byType(Switch));
    expect(switchWidget.dragStartBehavior, DragStartBehavior.start);

    await tester.pumpWidget(buildSwitchListTile(DragStartBehavior.down));
    final Switch switchWidget1 = tester.widget<Switch>(find.byType(Switch));
    expect(switchWidget1.dragStartBehavior, DragStartBehavior.down);
  });

  testWidgets('Switch on SwitchListTile changes mouse cursor when hovered', (WidgetTester tester) async {
    // Test SwitchListTile.adaptive() constructor
    await tester.pumpWidget(wrap(
      child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return SwitchListTile.adaptive(
              mouseCursor: SystemMouseCursors.text,
              value: false,
              onChanged: (_) {},
            );
          }),
    ));
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
    await gesture.addPointer(location: tester.getCenter(find.byType(Switch)));

    await tester.pump();

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

    // Test SwitchListTile() constructor
    await tester.pumpWidget(wrap(
      child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return SwitchListTile(
              mouseCursor: SystemMouseCursors.forbidden,
              value: false,
              onChanged: (_) {},
            );
          }),
    ));

    await gesture.moveTo(tester.getCenter(find.byType(Switch)));
    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);

    // Test default cursor
    await tester.pumpWidget(wrap(
      child: StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {
          return SwitchListTile(
            value: false,
            onChanged: (_) {},
          );
        }),
    ));

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

    // Test default cursor when disabled
    await tester.pumpWidget(wrap(
      child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return const SwitchListTile(
              value: false,
              onChanged: null,
            );
          }),
    ));

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

  testWidgets('Switch with splash radius set', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    const double splashRadius = 35;
    await tester.pumpWidget(wrap(
      child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return SwitchListTile(
              splashRadius: splashRadius,
              value: false,
              onChanged: (_) {},
            );
          }),
    ));

    await tester.pumpAndSettle();

    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.moveTo(tester.getCenter(find.byType(Switch)));

    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..circle(radius: splashRadius),
    );
  });

  testWidgets('The overlay color for the thumb of the switch resolves in active/pressed/hovered states', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    const Color activeThumbColor = Color(0xFF000000);
    const Color inactiveThumbColor = Color(0xFF000010);
    const Color activePressedOverlayColor = Color(0xFF000001);
    const Color inactivePressedOverlayColor = Color(0xFF000002);
    const Color hoverOverlayColor = Color(0xFF000003);
    const Color hoverColor = Color(0xFF000005);

    Color? getOverlayColor(Set<MaterialState> states) {
      if (states.contains(MaterialState.pressed)) {
        if (states.contains(MaterialState.selected)) {
          return activePressedOverlayColor;
        }
        return inactivePressedOverlayColor;
      }
      if (states.contains(MaterialState.hovered)) {
        return hoverOverlayColor;
      }
      return null;
    }

    Widget buildSwitch({bool active = false, bool focused = false, bool useOverlay = true}) {
      return MaterialApp(
        home: Scaffold(
          body: SwitchListTile(
            value: active,
            onChanged: (_) { },
            thumbColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
              if (states.contains(MaterialState.selected)) {
                return activeThumbColor;
              }
              return inactiveThumbColor;
            }),
            overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null,
            hoverColor: hoverColor,
          ),
        ),
      );
    }

    // test inactive Switch, and overlayColor is set to null.
    await tester.pumpWidget(buildSwitch(useOverlay: false));
    await tester.press(find.byType(Switch));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()
        ..circle(
          color: inactiveThumbColor.withAlpha(kRadialReactionAlpha),
        ),
      reason: 'Default inactive pressed Switch should have overlay color from thumbColor',
    );

    // test active Switch, and overlayColor is set to null.
    await tester.pumpWidget(buildSwitch(active: true, useOverlay: false));
    await tester.press(find.byType(Switch));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()
        ..circle(
          color: activeThumbColor.withAlpha(kRadialReactionAlpha),
        ),
      reason: 'Default active pressed Switch should have overlay color from thumbColor',
    );

    // test inactive Switch with an overlayColor
    await tester.pumpWidget(buildSwitch());
    await tester.press(find.byType(Switch));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()
        ..circle(
          color: inactivePressedOverlayColor,
        ),
      reason: 'Inactive pressed Switch should have overlay color: $inactivePressedOverlayColor',
    );

    // test active Switch with an overlayColor
    await tester.pumpWidget(buildSwitch(active: true));
    await tester.press(find.byType(Switch));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()
        ..circle(
          color: activePressedOverlayColor,
        ),
      reason: 'Active pressed Switch should have overlay color: $activePressedOverlayColor',
    );

    await tester.pumpWidget(buildSwitch(focused: true));
    await tester.pumpAndSettle();

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

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints
        ..rrect()
        ..circle(
          color: hoverOverlayColor,
        ),
      reason: 'Hovered Switch should use overlay color $hoverOverlayColor over $hoverColor',
    );
  });

  testWidgets('SwitchListTile respects trackOutlineColor in active/enabled states', (WidgetTester tester) async {
    const Color activeEnabledTrackOutlineColor = Color(0xFF000001);
    const Color activeDisabledTrackOutlineColor = Color(0xFF000002);
    const Color inactiveEnabledTrackOutlineColor = Color(0xFF000003);
    const Color inactiveDisabledTrackOutlineColor = Color(0xFF000004);

    Color getOutlineColor(Set<MaterialState> states) {
      if (states.contains(MaterialState.disabled)) {
        if (states.contains(MaterialState.selected)) {
          return activeDisabledTrackOutlineColor;
        }
        return inactiveDisabledTrackOutlineColor;
      }
      if (states.contains(MaterialState.selected)) {
        return activeEnabledTrackOutlineColor;
      }
      return inactiveEnabledTrackOutlineColor;
    }

    final MaterialStateProperty<Color> trackOutlineColor = MaterialStateColor.resolveWith(getOutlineColor);

    Widget buildSwitchListTile({required bool enabled, required bool selected}) {
      return wrap(
        child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return SwitchListTile(
                value: selected,
                trackOutlineColor: trackOutlineColor,
                onChanged: enabled ? (_) { } : null,
              );
            }),
      );
    }

    await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: false));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..rrect(style: PaintingStyle.fill)
        ..rrect(color: inactiveDisabledTrackOutlineColor, style: PaintingStyle.stroke),
    );

    await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: true));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..rrect(style: PaintingStyle.fill)
        ..rrect(color: activeDisabledTrackOutlineColor, style: PaintingStyle.stroke),
    );

    await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: false));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..rrect(style: PaintingStyle.fill)
        ..rrect(color: inactiveEnabledTrackOutlineColor, style: PaintingStyle.stroke),
    );

    await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: true));
    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..rrect(style: PaintingStyle.fill)
        ..rrect(color: activeEnabledTrackOutlineColor, style: PaintingStyle.stroke),
    );
  });

  testWidgets('SwitchListTile respects trackOutlineColor in hovered state', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    const Color hoveredTrackColor = Color(0xFF4caf50);

    Color getTrackOutlineColor(Set<MaterialState> states) {
      if (states.contains(MaterialState.hovered)) {
        return hoveredTrackColor;
      }
      return Colors.transparent;
    }

    final MaterialStateProperty<Color> outlineColor = MaterialStateColor.resolveWith(getTrackOutlineColor);

    Widget buildSwitchListTile() {
      return MaterialApp(
        theme: ThemeData(),
        home: wrap(
          child: StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                return SwitchListTile(
                  value: false,
                  trackOutlineColor: outlineColor,
                  onChanged: (_) { },
                );
              }),
        ),
      );
    }

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

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

    await tester.pumpAndSettle();

    expect(
      Material.of(tester.element(find.byType(Switch))),
      paints..rrect()..rrect(color: hoveredTrackColor, style: PaintingStyle.stroke)
    );
  });
}