// 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.

// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
library;

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

void main() {
  testWidgets('Switch can toggle on tap', (WidgetTester tester) async {
    final Key switchKey = UniqueKey();
    bool value = false;
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: CupertinoSwitch(
                key: switchKey,
                value: value,
                dragStartBehavior: DragStartBehavior.down,
                onChanged: (bool newValue) {
                  setState(() {
                    value = newValue;
                  });
                },
              ),
            );
          },
        ),
      ),
    );

    expect(value, isFalse);
    await tester.tap(find.byKey(switchKey));
    expect(value, isTrue);
  });

  testWidgets('CupertinoSwitch can be toggled by keyboard shortcuts', (WidgetTester tester) async {
    bool value = true;
    Widget buildApp({bool enabled = true}) {
      return CupertinoApp(
        home: CupertinoPageScaffold(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return CupertinoSwitch(
                value: value,
                onChanged: enabled ? (bool newValue) {
                  setState(() {
                    value = newValue;
                  });
                } : null,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());
    await tester.pumpAndSettle();
    expect(value, isTrue);
    await tester.sendKeyEvent(LogicalKeyboardKey.tab);
    await tester.pumpAndSettle();
    await tester.sendKeyEvent(LogicalKeyboardKey.space);
    await tester.pumpAndSettle();
    expect(value, isFalse);
    await tester.sendKeyEvent(LogicalKeyboardKey.space);
    await tester.pumpAndSettle();
    expect(value, isTrue);
  });

  testWidgets('Switch emits light haptic vibration on tap', (WidgetTester tester) async {
    final Key switchKey = UniqueKey();
    bool value = false;

    final List<MethodCall> log = <MethodCall>[];

    tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
      log.add(methodCall);
      return null;
    });

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: CupertinoSwitch(
                key: switchKey,
                value: value,
                dragStartBehavior: DragStartBehavior.down,
                onChanged: (bool newValue) {
                  setState(() {
                    value = newValue;
                  });
                },
              ),
            );
          },
        ),
      ),
    );

    await tester.tap(find.byKey(switchKey));
    await tester.pump();

    expect(log, hasLength(1));
    expect(log.single, isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'));
  }, variant: TargetPlatformVariant.only(TargetPlatform.iOS));

  testWidgets('Using other widgets that rebuild the switch will not cause vibrations', (WidgetTester tester) async {
    final Key switchKey = UniqueKey();
    final Key switchKey2 = UniqueKey();
    bool value = false;
    bool value2 = false;
    final List<MethodCall> log = <MethodCall>[];

    tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
      log.add(methodCall);
      return null;
    });

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: Column(
                children: <Widget>[
                  CupertinoSwitch(
                    key: switchKey,
                    value: value,
                    onChanged: (bool newValue) {
                      setState(() {
                        value = newValue;
                      });
                    },
                  ),
                  CupertinoSwitch(
                    key: switchKey2,
                    value: value2,
                    onChanged: (bool newValue) {
                      setState(() {
                        value2 = newValue;
                      });
                    },
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );

    await tester.tap(find.byKey(switchKey));
    await tester.pump();

    expect(log, hasLength(1));
    expect(log[0], isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'));

    await tester.tap(find.byKey(switchKey2));
    await tester.pump();

    expect(log, hasLength(2));
    expect(log[1], isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'));

    await tester.tap(find.byKey(switchKey));
    await tester.pump();

    expect(log, hasLength(3));
    expect(log[2], isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'));

    await tester.tap(find.byKey(switchKey2));
    await tester.pump();

    expect(log, hasLength(4));
    expect(log[3], isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'));
  }, variant: TargetPlatformVariant.only(TargetPlatform.iOS));

  testWidgets('Haptic vibration triggers on drag', (WidgetTester tester) async {
    bool value = false;
    final List<MethodCall> log = <MethodCall>[];

    tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
      log.add(methodCall);
      return null;
    });

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: CupertinoSwitch(
                value: value,
                dragStartBehavior: DragStartBehavior.down,
                onChanged: (bool newValue) {
                  setState(() {
                    value = newValue;
                  });
                },
              ),
            );
          },
        ),
      ),
    );

    await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
    expect(value, isTrue);
    await tester.pump();

    expect(log, hasLength(1));
    expect(log[0], isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.lightImpact'));
  }, variant: TargetPlatformVariant.only(TargetPlatform.iOS));

  testWidgets('No haptic vibration triggers from a programmatic value change', (WidgetTester tester) async {
    final Key switchKey = UniqueKey();
    bool value = false;

    final List<MethodCall> log = <MethodCall>[];
    tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
      log.add(methodCall);
      return null;
    });

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: Column(
                children: <Widget>[
                  CupertinoButton(
                    child: const Text('Button'),
                    onPressed: () {
                      setState(() {
                        value = !value;
                      });
                    },
                  ),
                  CupertinoSwitch(
                    key: switchKey,
                    value: value,
                    onChanged: (bool newValue) {
                      setState(() {
                        value = newValue;
                      });
                    },
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );

    expect(value, isFalse);

    await tester.tap(find.byType(CupertinoButton));
    expect(value, isTrue);
    await tester.pump();

    expect(log, hasLength(0));
  }, variant: TargetPlatformVariant.only(TargetPlatform.iOS));

  testWidgets('Switch can drag (LTR)', (WidgetTester tester) async {
    bool value = false;

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: CupertinoSwitch(
                value: value,
                onChanged: (bool newValue) {
                  setState(() {
                    value = newValue;
                  });
                },
              ),
            );
          },
        ),
      ),
    );

    expect(value, isFalse);

    await tester.drag(find.byType(CupertinoSwitch), const Offset(-48.0, 0.0));

    expect(value, isFalse);

    await tester.drag(find.byType(CupertinoSwitch), const Offset(48.0, 0.0));

    expect(value, isTrue);

    await tester.pump();
    await tester.drag(find.byType(CupertinoSwitch), const Offset(48.0, 0.0));

    expect(value, isTrue);

    await tester.pump();
    await tester.drag(find.byType(CupertinoSwitch), const Offset(-48.0, 0.0));

    expect(value, isFalse);
  });

  testWidgets('Switch can drag with dragStartBehavior', (WidgetTester tester) async {
    bool value = false;

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: CupertinoSwitch(
                value: value,
                dragStartBehavior: DragStartBehavior.down,
                onChanged: (bool newValue) {
                  setState(() {
                    value = newValue;
                  });
                },
              ),
            );
          },
        ),
      ),
    );

    expect(value, isFalse);
    await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
    expect(value, isFalse);

    await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
    expect(value, isTrue);
    await tester.pump();
    await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
    expect(value, isTrue);
    await tester.pump();
    await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
    expect(value, isFalse);

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: CupertinoSwitch(
                value: value,
                onChanged: (bool newValue) {
                  setState(() {
                    value = newValue;
                  });
                },
              ),
            );
          },
        ),
      ),
    );
    await tester.pumpAndSettle();
    final Rect switchRect = tester.getRect(find.byType(CupertinoSwitch));
    expect(value, isFalse);

    TestGesture gesture = await tester.startGesture(switchRect.center);
    // We have to execute the drag in two frames because the first update will
    // just set the start position.
    await gesture.moveBy(const Offset(20.0, 0.0));
    await gesture.moveBy(const Offset(20.0, 0.0));
    expect(value, isFalse);
    await gesture.up();
    expect(value, isTrue);
    await tester.pump();

    gesture = await tester.startGesture(switchRect.center);
    await gesture.moveBy(const Offset(20.0, 0.0));
    await gesture.moveBy(const Offset(20.0, 0.0));
    expect(value, isTrue);
    await gesture.up();
    await tester.pump();

    gesture = await tester.startGesture(switchRect.center);
    await gesture.moveBy(const Offset(-20.0, 0.0));
    await gesture.moveBy(const Offset(-20.0, 0.0));
    expect(value, isTrue);
    await gesture.up();
    expect(value, isFalse);
    await tester.pump();
  });

  testWidgets('Switch can drag (RTL)', (WidgetTester tester) async {
    bool value = false;

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.rtl,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: CupertinoSwitch(
                value: value,
                dragStartBehavior: DragStartBehavior.down,
                onChanged: (bool newValue) {
                  setState(() {
                    value = newValue;
                  });
                },
              ),
            );
          },
        ),
      ),
    );

    expect(value, isFalse);

    await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));

    expect(value, isFalse);

    await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));

    expect(value, isTrue);

    await tester.pump();
    await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));

    expect(value, isTrue);

    await tester.pump();
    await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));

    expect(value, isFalse);
  });

  testWidgets('can veto switch dragging result', (WidgetTester tester) async {
    bool value = false;

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Material(
              child: Center(
                child: CupertinoSwitch(
                  dragStartBehavior: DragStartBehavior.down,
                  value: value,
                  onChanged: (bool newValue) {
                    setState(() {
                      value = value || newValue;
                    });
                  },
                ),
              ),
            );
          },
        ),
      ),
    );

    // Move a little to the right, not past the middle.
    TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(CupertinoSwitch)).center);
    await gesture.moveBy(const Offset(kTouchSlop + 0.1, 0.0));
    await tester.pump();
    await gesture.moveBy(const Offset(-kTouchSlop + 5.1, 0.0));
    await tester.pump();
    await gesture.up();
    await tester.pump();
    expect(value, isFalse);
    final CurvedAnimation position = (tester.state(find.byType(CupertinoSwitch)) as dynamic).position as CurvedAnimation;
    expect(position.value, lessThan(0.5));
    await tester.pump();
    await tester.pumpAndSettle();
    expect(value, isFalse);
    expect(position.value, 0);

    // Move past the middle.
    gesture = await tester.startGesture(tester.getRect(find.byType(CupertinoSwitch)).center);
    await gesture.moveBy(const Offset(kTouchSlop + 0.1, 0.0));
    await tester.pump();
    await gesture.up();
    await tester.pump();
    expect(value, isTrue);
    expect(position.value, greaterThan(0.5));

    await tester.pump();
    await tester.pumpAndSettle();
    expect(value, isTrue);
    expect(position.value, 1.0);

    // Now move back to the left, the revert animation should play.
    gesture = await tester.startGesture(tester.getRect(find.byType(CupertinoSwitch)).center);
    await gesture.moveBy(const Offset(-kTouchSlop - 0.1, 0.0));
    await tester.pump();
    await gesture.up();
    await tester.pump();
    expect(value, isTrue);
    expect(position.value, lessThan(0.5));

    await tester.pump();
    await tester.pumpAndSettle();
    expect(value, isTrue);
    expect(position.value, 1.0);
  });

  testWidgets('Switch is translucent when disabled', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: CupertinoSwitch(
            value: false,
            dragStartBehavior: DragStartBehavior.down,
            onChanged: null,
          ),
        ),
      ),
    );

    expect(find.byType(Opacity), findsOneWidget);
    expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 0.5);
  });

  testWidgets('Switch is using track color when set', (WidgetTester tester) async {
    const Color trackColor = Color(0xFF00FF00);

    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: CupertinoSwitch(
            value: false,
            trackColor: trackColor,
            dragStartBehavior: DragStartBehavior.down,
            onChanged: null,
          ),
        ),
      ),
    );

    expect(find.byType(CupertinoSwitch), findsOneWidget);
    expect(tester.widget<CupertinoSwitch>(find.byType(CupertinoSwitch)).trackColor, trackColor);
    expect(find.byType(CupertinoSwitch), paints..rrect(color: trackColor));
  });

  testWidgets('Switch is using default thumb color', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: CupertinoSwitch(
            value: false,
            onChanged: null,
          ),
        ),
      ),
    );

    expect(find.byType(CupertinoSwitch), findsOneWidget);
    expect(tester.widget<CupertinoSwitch>(find.byType(CupertinoSwitch)).thumbColor, null);
    expect(
      find.byType(CupertinoSwitch),
      paints
        ..rrect()
        ..rrect()
        ..rrect()
        ..rrect()
        ..rrect(color: CupertinoColors.white),
    );
  });

  testWidgets('Switch is using thumb color when set', (WidgetTester tester) async {
    const Color thumbColor = Color(0xFF000000);
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: CupertinoSwitch(
            value: false,
            thumbColor: thumbColor,
            onChanged: null,
          ),
        ),
      ),
    );

    expect(find.byType(CupertinoSwitch), findsOneWidget);
    expect(tester.widget<CupertinoSwitch>(find.byType(CupertinoSwitch)).thumbColor, thumbColor);
    expect(
      find.byType(CupertinoSwitch),
      paints
        ..rrect()
        ..rrect()
        ..rrect()
        ..rrect()
        ..rrect(color: thumbColor),
    );
  });

  testWidgets('Switch is opaque when enabled', (WidgetTester tester) async {
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: CupertinoSwitch(
            value: false,
            dragStartBehavior: DragStartBehavior.down,
            onChanged: (bool newValue) {},
          ),
        ),
      ),
    );

    expect(find.byType(Opacity), findsOneWidget);
    expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 1.0);
  });

  testWidgets('Switch turns translucent after becoming disabled', (WidgetTester tester) async {
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: CupertinoSwitch(
            value: false,
            dragStartBehavior: DragStartBehavior.down,
            onChanged: (bool newValue) {},
          ),
        ),
      ),
    );

    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: CupertinoSwitch(
            value: false,
            dragStartBehavior: DragStartBehavior.down,
            onChanged: null,
          ),
        ),
      ),
    );

    expect(find.byType(Opacity), findsOneWidget);
    expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 0.5);
  });

  testWidgets('Switch turns opaque after becoming enabled', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: CupertinoSwitch(
            value: false,
            dragStartBehavior: DragStartBehavior.down,
            onChanged: null,
          ),
        ),
      ),
    );

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: CupertinoSwitch(
            value: false,
            dragStartBehavior: DragStartBehavior.down,
            onChanged: (bool newValue) {},
          ),
        ),
      ),
    );

    expect(find.byType(Opacity), findsOneWidget);
    expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 1.0);
  });

  testWidgets('Switch renders correctly before, during, and after being tapped', (WidgetTester tester) async {
    final Key switchKey = UniqueKey();
    bool value = false;
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: RepaintBoundary(
                child: CupertinoSwitch(
                  key: switchKey,
                  value: value,
                  dragStartBehavior: DragStartBehavior.down,
                  onChanged: (bool newValue) {
                    setState(() {
                      value = newValue;
                    });
                  },
                ),
              ),
            );
          },
        ),
      ),
    );

    await expectLater(
      find.byKey(switchKey),
      matchesGoldenFile('switch.tap.off.png'),
    );

    await tester.tap(find.byKey(switchKey));
    expect(value, isTrue);

    // Kick off animation, then advance to intermediate frame.
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 60));
    await expectLater(
      find.byKey(switchKey),
      matchesGoldenFile('switch.tap.turningOn.png'),
    );

    await tester.pumpAndSettle();
    await expectLater(
      find.byKey(switchKey),
      matchesGoldenFile('switch.tap.on.png'),
    );
  });

  PaintPattern onLabelPaintPattern({
    required int alpha,
    bool isRtl = false,
  }) =>
      paints
        ..rect(
          rect: Rect.fromLTWH(isRtl ? 43.5 : 14.5, 14.5, 1.0, 10.0),
          color: const Color(0xffffffff).withAlpha(alpha),
          style: PaintingStyle.fill,
        );

  PaintPattern offLabelPaintPattern({
    required int alpha,
    bool highContrast = false,
    bool isRtl = false,
  }) =>
      paints
        ..circle(
          x: isRtl ? 16.0 : 43.0,
          y: 19.5,
          radius: 5.0,
          color:
              (highContrast ? const Color(0xffffffff) : const Color(0xffb3b3b3))
                  .withAlpha(alpha),
          strokeWidth: 1.0,
          style: PaintingStyle.stroke,
        );

  testWidgets('Switch renders switch labels correctly before, during, and after being tapped', (WidgetTester tester) async {
    final Key switchKey = UniqueKey();
    bool value = false;
    await tester.pumpWidget(
      MediaQuery(
        data: const MediaQueryData(onOffSwitchLabels: true),
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Center(
                child: RepaintBoundary(
                  child: CupertinoSwitch(
                    key: switchKey,
                    value: value,
                    dragStartBehavior: DragStartBehavior.down,
                    onChanged: (bool newValue) {
                      setState(() {
                        value = newValue;
                      });
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    final RenderObject switchRenderObject =
        tester.element(find.byType(CupertinoSwitch)).renderObject!;

    expect(switchRenderObject, offLabelPaintPattern(alpha: 255));
    expect(switchRenderObject, onLabelPaintPattern(alpha: 0));

    await tester.tap(find.byKey(switchKey));
    expect(value, isTrue);

    // Kick off animation, then advance to intermediate frame.
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 60));
    expect(switchRenderObject, onLabelPaintPattern(alpha: 131));
    expect(switchRenderObject, offLabelPaintPattern(alpha: 124));

    await tester.pumpAndSettle();
    expect(switchRenderObject, onLabelPaintPattern(alpha: 255));
    expect(switchRenderObject, offLabelPaintPattern(alpha: 0));
  });

  testWidgets('Switch renders switch labels correctly before, during, and after being tapped in high contrast', (WidgetTester tester) async {
    final Key switchKey = UniqueKey();
    bool value = false;
    await tester.pumpWidget(
      MediaQuery(
        data: const MediaQueryData(
          onOffSwitchLabels: true,
          highContrast: true,
        ),
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Center(
                child: RepaintBoundary(
                  child: CupertinoSwitch(
                    key: switchKey,
                    value: value,
                    dragStartBehavior: DragStartBehavior.down,
                    onChanged: (bool newValue) {
                      setState(() {
                        value = newValue;
                      });
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    final RenderObject switchRenderObject =
        tester.element(find.byType(CupertinoSwitch)).renderObject!;

    expect(switchRenderObject, offLabelPaintPattern(highContrast: true, alpha: 255));
    expect(switchRenderObject, onLabelPaintPattern(alpha: 0));

    await tester.tap(find.byKey(switchKey));
    expect(value, isTrue);

    // Kick off animation, then advance to intermediate frame.
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 60));
    expect(switchRenderObject, onLabelPaintPattern(alpha: 131));
    expect(switchRenderObject, offLabelPaintPattern(highContrast: true, alpha: 124));

    await tester.pumpAndSettle();
    expect(switchRenderObject, onLabelPaintPattern(alpha: 255));
    expect(switchRenderObject, offLabelPaintPattern(highContrast: true, alpha: 0));
  });

  testWidgets('Switch renders switch labels correctly before, during, and after being tapped with direction rtl', (WidgetTester tester) async {
    final Key switchKey = UniqueKey();
    bool value = false;
    await tester.pumpWidget(
      MediaQuery(
        data: const MediaQueryData(onOffSwitchLabels: true),
        child: Directionality(
          textDirection: TextDirection.rtl,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Center(
                child: RepaintBoundary(
                  child: CupertinoSwitch(
                    key: switchKey,
                    value: value,
                    dragStartBehavior: DragStartBehavior.down,
                    onChanged: (bool newValue) {
                      setState(() {
                        value = newValue;
                      });
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    final RenderObject switchRenderObject =
        tester.element(find.byType(CupertinoSwitch)).renderObject!;

    expect(switchRenderObject, offLabelPaintPattern(isRtl: true, alpha: 255));
    expect(switchRenderObject, onLabelPaintPattern(isRtl: true, alpha: 0));

    await tester.tap(find.byKey(switchKey));
    expect(value, isTrue);

    // Kick off animation, then advance to intermediate frame.
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 60));
    expect(switchRenderObject, onLabelPaintPattern(isRtl: true, alpha: 131));
    expect(switchRenderObject, offLabelPaintPattern(isRtl: true, alpha: 124));

    await tester.pumpAndSettle();
    expect(switchRenderObject, onLabelPaintPattern(isRtl: true, alpha: 255));
    expect(switchRenderObject, offLabelPaintPattern(isRtl: true, alpha: 0));
  });

  testWidgets('Switch renders correctly in dark mode', (WidgetTester tester) async {
    final Key switchKey = UniqueKey();
    bool value = false;
    await tester.pumpWidget(
      MediaQuery(
        data: const MediaQueryData(platformBrightness: Brightness.dark),
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Center(
                child: RepaintBoundary(
                  child: CupertinoSwitch(
                    key: switchKey,
                    value: value,
                    dragStartBehavior: DragStartBehavior.down,
                    onChanged: (bool newValue) {
                      setState(() {
                        value = newValue;
                      });
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    await expectLater(
      find.byKey(switchKey),
      matchesGoldenFile('switch.tap.off.dark.png'),
    );

    await tester.tap(find.byKey(switchKey));
    expect(value, isTrue);

    await tester.pumpAndSettle();
    await expectLater(
      find.byKey(switchKey),
      matchesGoldenFile('switch.tap.on.dark.png'),
    );
  });

  testWidgets('Switch can apply the ambient theme and be opted out', (WidgetTester tester) async {
    final Key switchKey = UniqueKey();
    bool value = false;
    await tester.pumpWidget(
      CupertinoTheme(
        data: const CupertinoThemeData(primaryColor: Colors.amber, applyThemeToAll: true),
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Center(
                child: RepaintBoundary(
                  child: Column(
                    children: <Widget>[
                      CupertinoSwitch(
                        key: switchKey,
                        value: value,
                        dragStartBehavior: DragStartBehavior.down,
                        applyTheme: true,
                        onChanged: (bool newValue) {
                          setState(() {
                            value = newValue;
                          });
                        },
                      ),
                      CupertinoSwitch(
                        value: value,
                        dragStartBehavior: DragStartBehavior.down,
                        applyTheme: false,
                        onChanged: (bool newValue) {
                          setState(() {
                            value = newValue;
                          });
                        },
                      ),
                    ],
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    await expectLater(
      find.byType(Column),
      matchesGoldenFile('switch.tap.off.themed.png'),
    );

    await tester.tap(find.byKey(switchKey));
    expect(value, isTrue);

    await tester.pumpAndSettle();
    await expectLater(
      find.byType(Column),
      matchesGoldenFile('switch.tap.on.themed.png'),
    );
  });

  testWidgets('Hovering over Cupertino switch updates cursor to clickable on Web', (WidgetTester tester) async {
    const bool value = false;
    // Disabled CupertinoSwitch does not update cursor on Web.
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return const Center(
              child: CupertinoSwitch(
                value: value,
                dragStartBehavior: DragStartBehavior.down,
                onChanged: null,
              ),
            );
          },
        ),
      ),
    );

    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
    final Offset cupertinoSwitch = tester.getCenter(find.byType(CupertinoSwitch));
    await gesture.addPointer(location: cupertinoSwitch);
    await tester.pumpAndSettle();
    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);

    // Enabled CupertinoSwitch updates cursor when hovering on Web.
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: CupertinoSwitch(
                value: value,
                dragStartBehavior: DragStartBehavior.down,
                onChanged: (bool newValue) { },
              ),
            );
          },
        ),
      ),
    );

    await gesture.moveTo(const Offset(10, 10));
    await tester.pumpAndSettle();
    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);

    await gesture.moveTo(cupertinoSwitch);
    await tester.pumpAndSettle();
    expect(
      RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
      kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
    );
  });

  testWidgets('CupertinoSwitch is focusable and has correct focus color', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode(debugLabel: 'CupertinoSwitch');
    addTearDown(focusNode.dispose);
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    bool value = true;
    const Color focusColor = Color(0xffff0000);

    Widget buildApp({bool enabled = true}) {
      return Directionality(
        textDirection: TextDirection.ltr,
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: CupertinoSwitch(
                value: value,
                onChanged: enabled ? (bool newValue) {
                  setState(() {
                    value = newValue;
                  });
                } : null,
                focusColor: focusColor,
                focusNode: focusNode,
                autofocus: true,
              ),
            );
          },
        ),
      );
    }

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

    expect(focusNode.hasPrimaryFocus, isTrue);
    expect(
      find.byType(CupertinoSwitch),
      paints
        ..rrect(color: const Color(0xff34c759))
        ..rrect(color: focusColor)
        ..clipRRect()
        ..rrect(color: const Color(0x26000000))
        ..rrect(color: const Color(0x0f000000))
        ..rrect(color: const Color(0x0a000000))
        ..rrect(color: const Color(0xffffffff)),
    );

    // Check the false value.
    value = false;
    await tester.pumpWidget(buildApp());
    await tester.pumpAndSettle();

    expect(focusNode.hasPrimaryFocus, isTrue);
    expect(
      find.byType(CupertinoSwitch),
      paints
        ..rrect(color: const Color(0x28787880))
        ..rrect(color: focusColor)
        ..clipRRect()
        ..rrect(color: const Color(0x26000000))
        ..rrect(color: const Color(0x0f000000))
        ..rrect(color: const Color(0x0a000000))
        ..rrect(color: const Color(0xffffffff)),
    );

    // Check what happens when disabled.
    value = false;
    await tester.pumpWidget(buildApp(enabled: false));
    await tester.pumpAndSettle();

    expect(focusNode.hasPrimaryFocus, isFalse);
    expect(
      find.byType(CupertinoSwitch),
      paints
        ..rrect(color: const Color(0x28787880))
        ..clipRRect()
        ..rrect(color: const Color(0x26000000))
        ..rrect(color: const Color(0x0f000000))
        ..rrect(color: const Color(0x0a000000))
        ..rrect(color: const Color(0xffffffff)),
    );
  });

  testWidgets('CupertinoSwitch.onFocusChange callback', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode(debugLabel: 'CupertinoSwitch');
    addTearDown(focusNode.dispose);
    bool focused = false;
    await tester.pumpWidget(
      Directionality(
      textDirection: TextDirection.ltr,
      child: Center(
        child: CupertinoSwitch(
            value: true,
            focusNode: focusNode,
            onFocusChange: (bool value) {
              focused = value;
            },
            onChanged:(bool newValue) {},
          ),
        ),
      ),
    );

    focusNode.requestFocus();
    await tester.pump();
    expect(focused, isTrue);
    expect(focusNode.hasFocus, isTrue);

    focusNode.unfocus();
    await tester.pump();
    expect(focused, isFalse);
    expect(focusNode.hasFocus, isFalse);
  });
}
