// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:ui';

import 'package:flutter/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/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/src/physics/utils.dart' show nearEqual;
import 'package:flutter_test/flutter_test.dart';

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

// A thumb shape that also logs its repaint center.
class LoggingThumbShape extends SliderComponentShape {
  LoggingThumbShape(this.log);

  final List<Offset> log;

  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    return const Size(10.0, 10.0);
  }

  @override
  void paint(
    PaintingContext context,
    Offset thumbCenter, {
    required Animation<double> activationAnimation,
    required Animation<double> enableAnimation,
    required bool isDiscrete,
    required TextPainter labelPainter,
    required RenderBox parentBox,
    required SliderThemeData sliderTheme,
    required TextDirection textDirection,
    required double value,
    required double textScaleFactor,
    required Size sizeWithOverflow,
  }) {
    log.add(thumbCenter);
    final Paint thumbPaint = Paint()..color = Colors.red;
    context.canvas.drawCircle(thumbCenter, 5.0, thumbPaint);
  }
}

class TallSliderTickMarkShape extends SliderTickMarkShape {
  @override
  Size getPreferredSize({required SliderThemeData sliderTheme, required bool isEnabled}) {
    return const Size(10.0, 200.0);
  }

  @override
  void paint(
    PaintingContext context,
    Offset offset, {
    required Offset thumbCenter,
    required RenderBox parentBox,
    required SliderThemeData sliderTheme,
    required Animation<double> enableAnimation,
    required bool isEnabled,
    required TextDirection textDirection,
  }) {
    final Paint paint = Paint()..color = Colors.red;
    context.canvas.drawRect(Rect.fromLTWH(offset.dx, offset.dy, 10.0, 20.0), paint);
  }
}

class _StateDependentMouseCursor extends MaterialStateMouseCursor {
  const _StateDependentMouseCursor({
    this.disabled = SystemMouseCursors.none,
    this.dragged = SystemMouseCursors.none,
    this.hovered = SystemMouseCursors.none,
  });

  final MouseCursor disabled;
  final MouseCursor hovered;
  final MouseCursor dragged;

  @override
  MouseCursor resolve(Set<MaterialState> states) {
    if (states.contains(MaterialState.disabled)) {
      return disabled;
    }
    if (states.contains(MaterialState.dragged)) {
      return dragged;
    }
    if (states.contains(MaterialState.hovered)) {
      return hovered;
    }
    return SystemMouseCursors.none;
  }

  @override
  String get debugDescription => '_StateDependentMouseCursor';
}

void main() {
  testWidgets('The initial value should respect the discrete value', (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.20;
    final List<Offset> log = <Offset>[];
    final LoggingThumbShape loggingThumb = LoggingThumbShape(log);
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              final SliderThemeData sliderTheme = SliderTheme.of(context).copyWith(thumbShape: loggingThumb);
              return Material(
                child: Center(
                  child: SliderTheme(
                    data: sliderTheme,
                    child: Slider(
                      key: sliderKey,
                      value: value,
                      divisions: 4,
                      onChanged: (double newValue) {
                        setState(() {
                          value = newValue;
                        });
                      },
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(value, equals(0.20));
    expect(log.length, 1);
    expect(log[0], const Offset(212.0, 300.0));
  });

  testWidgets('Slider can move when tapped (LTR)', (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.0;
    double? startValue;
    double? endValue;

    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: Center(
                  child: Slider(
                    key: sliderKey,
                    value: value,
                    onChanged: (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    },
                    onChangeStart: (double value) {
                      startValue = value;
                    },
                    onChangeEnd: (double value) {
                      endValue = value;
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(value, equals(0.0));
    await tester.tap(find.byKey(sliderKey));
    expect(value, equals(0.5));
    expect(startValue, equals(0.0));
    expect(endValue, equals(0.5));
    startValue = null;
    endValue = null;
    await tester.pump(); // No animation should start.
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));

    final Offset topLeft = tester.getTopLeft(find.byKey(sliderKey));
    final Offset bottomRight = tester.getBottomRight(find.byKey(sliderKey));

    final Offset target = topLeft + (bottomRight - topLeft) / 4.0;
    await tester.tapAt(target);
    expect(value, moreOrLessEquals(0.25, epsilon: 0.05));
    expect(startValue, equals(0.5));
    expect(endValue, moreOrLessEquals(0.25, epsilon: 0.05));
    await tester.pump(); // No animation should start.
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
  });

  testWidgets('Slider can move when tapped (RTL)', (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.0;

    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.rtl,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: Center(
                  child: Slider(
                    key: sliderKey,
                    value: value,
                    onChanged: (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(value, equals(0.0));
    await tester.tap(find.byKey(sliderKey));
    expect(value, equals(0.5));
    await tester.pump(); // No animation should start.
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));

    final Offset topLeft = tester.getTopLeft(find.byKey(sliderKey));
    final Offset bottomRight = tester.getBottomRight(find.byKey(sliderKey));

    final Offset target = topLeft + (bottomRight - topLeft) / 4.0;
    await tester.tapAt(target);
    expect(value, moreOrLessEquals(0.75, epsilon: 0.05));
    await tester.pump(); // No animation should start.
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
  });

  testWidgets("Slider doesn't send duplicate change events if tapped on the same value", (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.0;
    late double startValue;
    late double endValue;
    int updates = 0;
    int startValueUpdates = 0;
    int endValueUpdates = 0;

    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: Center(
                  child: Slider(
                    key: sliderKey,
                    value: value,
                    onChanged: (double newValue) {
                      setState(() {
                        updates++;
                        value = newValue;
                      });
                    },
                    onChangeStart: (double value) {
                      startValueUpdates++;
                      startValue = value;
                    },
                    onChangeEnd: (double value) {
                      endValueUpdates++;
                      endValue = value;
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(value, equals(0.0));
    await tester.tap(find.byKey(sliderKey));
    expect(value, equals(0.5));
    expect(startValue, equals(0.0));
    expect(endValue, equals(0.5));
    await tester.pump();
    await tester.tap(find.byKey(sliderKey));
    expect(value, equals(0.5));
    await tester.pump();
    expect(updates, equals(1));
    expect(startValueUpdates, equals(2));
    expect(endValueUpdates, equals(2));
  });

  testWidgets('Value indicator shows for a bit after being tapped', (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.0;

    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: Center(
                  child: Slider(
                    key: sliderKey,
                    value: value,
                    divisions: 4,
                    onChanged: (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(value, equals(0.0));
    await tester.tap(find.byKey(sliderKey));
    expect(value, equals(0.5));
    await tester.pump(const Duration(milliseconds: 100));
    // Starts with the position animation and value indicator
    expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
    await tester.pump(const Duration(milliseconds: 100));
    // Value indicator is longer than position.
    expect(SchedulerBinding.instance.transientCallbackCount, equals(1));
    await tester.pump(const Duration(milliseconds: 100));
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
    await tester.pump(const Duration(milliseconds: 100));
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
    await tester.pump(const Duration(milliseconds: 100));
    // Shown for long enough, value indicator is animated closed.
    expect(SchedulerBinding.instance.transientCallbackCount, equals(1));
    await tester.pump(const Duration(milliseconds: 101));
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
  });

  testWidgets('Discrete Slider repaints and animates when dragged', (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.0;
    final List<Offset> log = <Offset>[];
    final LoggingThumbShape loggingThumb = LoggingThumbShape(log);
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              final SliderThemeData sliderTheme = SliderTheme.of(context).copyWith(thumbShape: loggingThumb);
              return Material(
                child: Center(
                  child: SliderTheme(
                    data: sliderTheme,
                    child: Slider(
                      key: sliderKey,
                      value: value,
                      divisions: 4,
                      onChanged: (double newValue) {
                        setState(() {
                          value = newValue;
                        });
                      },
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    final List<Offset> expectedLog = <Offset>[
      const Offset(24.0, 300.0),
      const Offset(24.0, 300.0),
      const Offset(400.0, 300.0),
    ];
    final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));
    expect(value, equals(0.5));
    expect(log.length, 3);
    expect(log, orderedEquals(expectedLog));
    await gesture.moveBy(const Offset(-500.0, 0.0));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 10));
    expect(value, equals(0.0));
    expect(log.length, 5);
    expect(log.last.dx, moreOrLessEquals(386.6, epsilon: 0.1));
    // With no more gesture or value changes, the thumb position should still
    // be redrawn in the animated position.
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 10));
    expect(value, equals(0.0));
    expect(log.length, 7);
    expect(log.last.dx, moreOrLessEquals(344.5, epsilon: 0.1));
    // Final position.
    await tester.pump(const Duration(milliseconds: 80));
    expectedLog.add(const Offset(24.0, 300.0));
    expect(value, equals(0.0));
    expect(log.length, 8);
    expect(log.last.dx, moreOrLessEquals(24.0, epsilon: 0.1));
    await gesture.up();
  });

  testWidgets("Slider doesn't send duplicate change events if tapped on the same value", (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.0;
    int updates = 0;

    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: Center(
                  child: Slider(
                    key: sliderKey,
                    value: value,
                    onChanged: (double newValue) {
                      setState(() {
                        updates++;
                        value = newValue;
                      });
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(value, equals(0.0));
    await tester.tap(find.byKey(sliderKey));
    expect(value, equals(0.5));
    await tester.pump();
    await tester.tap(find.byKey(sliderKey));
    expect(value, equals(0.5));
    await tester.pump();
    expect(updates, equals(1));
  });

  testWidgets('discrete Slider repaints when dragged', (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.0;
    final List<Offset> log = <Offset>[];
    final LoggingThumbShape loggingThumb = LoggingThumbShape(log);
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              final SliderThemeData sliderTheme = SliderTheme.of(context).copyWith(thumbShape: loggingThumb);
              return Material(
                child: Center(
                  child: SliderTheme(
                    data: sliderTheme,
                    child: Slider(
                      key: sliderKey,
                      value: value,
                      divisions: 4,
                      onChanged: (double newValue) {
                        setState(() {
                          value = newValue;
                        });
                      },
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    final List<Offset> expectedLog = <Offset>[
      const Offset(24.0, 300.0),
      const Offset(24.0, 300.0),
      const Offset(400.0, 300.0),
    ];
    final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));
    expect(value, equals(0.5));
    expect(log.length, 3);
    expect(log, orderedEquals(expectedLog));
    await gesture.moveBy(const Offset(-500.0, 0.0));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 10));
    expect(value, equals(0.0));
    expect(log.length, 5);
    expect(log.last.dx, moreOrLessEquals(386.6, epsilon: 0.1));
    // With no more gesture or value changes, the thumb position should still
    // be redrawn in the animated position.
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 10));
    expect(value, equals(0.0));
    expect(log.length, 7);
    expect(log.last.dx, moreOrLessEquals(344.5, epsilon: 0.1));
    // Final position.
    await tester.pump(const Duration(milliseconds: 80));
    expectedLog.add(const Offset(24.0, 300.0));
    expect(value, equals(0.0));
    expect(log.length, 8);
    expect(log.last.dx, moreOrLessEquals(24.0, epsilon: 0.1));
    await gesture.up();
  });

  testWidgets('Slider take on discrete values', (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.0;

    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: Center(
                  child: SizedBox(
                    width: 144.0 + 2 * 16.0, // _kPreferredTotalWidth
                    child: Slider(
                      key: sliderKey,
                      max: 100.0,
                      divisions: 10,
                      value: value,
                      onChanged: (double newValue) {
                        setState(() {
                          value = newValue;
                        });
                      },
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(value, equals(0.0));
    await tester.tap(find.byKey(sliderKey));
    expect(value, equals(50.0));
    await tester.drag(find.byKey(sliderKey), const Offset(5.0, 0.0));
    expect(value, equals(50.0));
    await tester.drag(find.byKey(sliderKey), const Offset(40.0, 0.0));
    expect(value, equals(80.0));

    await tester.pump(); // Starts animation.
    expect(SchedulerBinding.instance.transientCallbackCount, greaterThan(0));
    await tester.pump(const Duration(milliseconds: 200));
    await tester.pump(const Duration(milliseconds: 200));
    await tester.pump(const Duration(milliseconds: 200));
    await tester.pump(const Duration(milliseconds: 200));
    // Animation complete.
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
  });

  testWidgets('Slider can be given zero values', (WidgetTester tester) async {
    final List<double> log = <double>[];
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Slider(
              value: 0.0,
              onChanged: (double newValue) {
                log.add(newValue);
              },
            ),
          ),
        ),
      ),
    );

    await tester.tap(find.byType(Slider));
    expect(log, <double>[0.5]);
    log.clear();

    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Slider(
              value: 0.0,
              max: 0.0,
              onChanged: (double newValue) {
                log.add(newValue);
              },
            ),
          ),
        ),
      ),
    );

    await tester.tap(find.byType(Slider));
    expect(log, <double>[]);
    log.clear();
  });

  testWidgets('Slider can tap in vertical scroller', (WidgetTester tester) async {
    double value = 0.0;
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: ListView(
              children: <Widget>[
                Slider(
                  value: value,
                  onChanged: (double newValue) {
                    value = newValue;
                  },
                ),
                Container(
                  height: 2000.0,
                ),
              ],
            ),
          ),
        ),
      ),
    );

    await tester.tap(find.byType(Slider));
    expect(value, equals(0.5));
  });

  testWidgets('Slider drags immediately (LTR)', (WidgetTester tester) async {
    double value = 0.0;
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: Slider(
                value: value,
                onChanged: (double newValue) {
                  value = newValue;
                },
              ),
            ),
          ),
        ),
      ),
    );

    final Offset center = tester.getCenter(find.byType(Slider));
    final TestGesture gesture = await tester.startGesture(center);

    expect(value, equals(0.5));

    await gesture.moveBy(const Offset(1.0, 0.0));

    expect(value, greaterThan(0.5));

    await gesture.up();
  });

  testWidgets('Slider drags immediately (RTL)', (WidgetTester tester) async {
    double value = 0.0;
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.rtl,
          child: Material(
            child: Center(
              child: Slider(
                value: value,
                onChanged: (double newValue) {
                  value = newValue;
                },
              ),
            ),
          ),
        ),
      ),
    );

    final Offset center = tester.getCenter(find.byType(Slider));
    final TestGesture gesture = await tester.startGesture(center);

    expect(value, equals(0.5));

    await gesture.moveBy(const Offset(1.0, 0.0));

    expect(value, lessThan(0.5));

    await gesture.up();
  });

  testWidgets('Slider onChangeStart and onChangeEnd fire once', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/28115

    int startFired = 0;
    int endFired = 0;
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: GestureDetector(
                onHorizontalDragUpdate: (_) { },
                child: Slider(
                  value: 0.0,
                  onChanged: (double newValue) { },
                  onChangeStart: (double value) {
                    startFired += 1;
                  },
                  onChangeEnd: (double value) {
                    endFired += 1;
                  },
                ),
              ),
            ),
          ),
        ),
      ),
    );

    await tester.timedDrag(
      find.byType(Slider),
      const Offset(20.0, 0.0),
      const Duration(milliseconds: 100),
    );

    expect(startFired, equals(1));
    expect(endFired, equals(1));
  });

  testWidgets('Slider sizing', (WidgetTester tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: Slider(
                value: 0.5,
                onChanged: null,
              ),
            ),
          ),
        ),
      ),
    );
    expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(800.0, 600.0));

    await tester.pumpWidget(
      const MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: IntrinsicWidth(
                child: Slider(
                  value: 0.5,
                  onChanged: null,
                ),
              ),
            ),
          ),
        ),
      ),
    );
    expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(144.0 + 2.0 * 24.0, 600.0));

    await tester.pumpWidget(
      const MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: OverflowBox(
                maxWidth: double.infinity,
                maxHeight: double.infinity,
                child: Slider(
                  value: 0.5,
                  onChanged: null,
                ),
              ),
            ),
          ),
        ),
      ),
    );
    expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(144.0 + 2.0 * 24.0, 48.0));
  });

  testWidgets('Slider respects textScaleFactor', (WidgetTester tester) async {
    debugDisableShadows = false;
    try {
      final Key sliderKey = UniqueKey();
      double value = 0.0;

      Widget buildSlider({
        required double textScaleFactor,
        bool isDiscrete = true,
        ShowValueIndicator show = ShowValueIndicator.onlyForDiscrete,
      }) {
        return MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Directionality(
            textDirection: TextDirection.ltr,
            child: StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                return MediaQuery(
                  data: MediaQueryData(textScaleFactor: textScaleFactor),
                  child: Material(
                    child: Theme(
                      data: Theme.of(context).copyWith(
                        sliderTheme: Theme.of(context).sliderTheme.copyWith(showValueIndicator: show),
                      ),
                      child: Center(
                        child: OverflowBox(
                          maxWidth: double.infinity,
                          maxHeight: double.infinity,
                          child: Slider(
                            key: sliderKey,
                            max: 100.0,
                            divisions: isDiscrete ? 10 : null,
                            label: '${value.round()}',
                            value: value,
                            onChanged: (double newValue) {
                              setState(() {
                                value = newValue;
                              });
                            },
                          ),
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
        );
      }

      await tester.pumpWidget(buildSlider(textScaleFactor: 1.0));
      Offset center = tester.getCenter(find.byType(Slider));
      TestGesture gesture = await tester.startGesture(center);
      await tester.pumpAndSettle();

      expect(
        tester.renderObject(find.byType(Overlay)),
        paints
          ..path(
            includes: const <Offset>[
              Offset.zero,
              Offset(0.0, -8.0),
              Offset(-276.0, -16.0),
              Offset(-216.0, -16.0),
            ],
            color: const Color(0xf55f5f5f),
          )
          ..paragraph(),
      );

      await gesture.up();
      await tester.pumpAndSettle();

      await tester.pumpWidget(buildSlider(textScaleFactor: 2.0));
      center = tester.getCenter(find.byType(Slider));
      gesture = await tester.startGesture(center);
      await tester.pumpAndSettle();

      expect(
        tester.renderObject(find.byType(Overlay)),
        paints
          ..path(
            includes: const <Offset>[
              Offset.zero,
              Offset(0.0, -8.0),
              Offset(-304.0, -16.0),
              Offset(-216.0, -16.0),
            ],
            color: const Color(0xf55f5f5f),
          )
          ..paragraph(),
      );

      await gesture.up();
      await tester.pumpAndSettle();

      // Check continuous
      await tester.pumpWidget(buildSlider(
        textScaleFactor: 1.0,
        isDiscrete: false,
        show: ShowValueIndicator.onlyForContinuous,
      ));
      center = tester.getCenter(find.byType(Slider));
      gesture = await tester.startGesture(center);
      await tester.pumpAndSettle();

      expect(tester.renderObject(find.byType(Overlay)),
        paints
          ..path(
            includes: const <Offset>[
              Offset.zero,
              Offset(0.0, -8.0),
              Offset(-276.0, -16.0),
              Offset(-216.0, -16.0),
            ],
            color: const Color(0xf55f5f5f),
          )
          ..paragraph(),
      );

      await gesture.up();
      await tester.pumpAndSettle();

      await tester.pumpWidget(buildSlider(
        textScaleFactor: 2.0,
        isDiscrete: false,
        show: ShowValueIndicator.onlyForContinuous,
      ));
      center = tester.getCenter(find.byType(Slider));
      gesture = await tester.startGesture(center);
      await tester.pumpAndSettle();

      expect(
        tester.renderObject(find.byType(Overlay)),
        paints
          ..path(
            includes: const <Offset>[
              Offset.zero,
              Offset(0.0, -8.0),
              Offset(-276.0, -16.0),
              Offset(-216.0, -16.0),
            ],
            color: const Color(0xf55f5f5f),
          )
          ..paragraph(),
      );

      await gesture.up();
      await tester.pumpAndSettle();
    } finally {
      debugDisableShadows = true;
    }
  });

  testWidgets('Tick marks are skipped when they are too dense', (WidgetTester tester) async {
    Widget buildSlider({
      required int divisions,
    }) {
      return MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: Slider(
                max: 100.0,
                divisions: divisions,
                value: 0.25,
                onChanged: (double newValue) { },
              ),
            ),
          ),
        ),
      );
    }

    // Pump a slider with a reasonable amount of divisions to verify that the
    // tick marks are drawn when the number of tick marks is not too dense.
    await tester.pumpWidget(
      buildSlider(
        divisions: 4,
      ),
    );

    final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));

    // 5 tick marks and a thumb.
    expect(material, paintsExactlyCountTimes(#drawCircle, 6));

    // 200 divisions will produce a tick interval off less than 6,
    // which would be too dense to draw.
    await tester.pumpWidget(
      buildSlider(
        divisions: 200,
      ),
    );

    // No tick marks are drawn because they are too dense, but the thumb is
    // still drawn.
    expect(material, paintsExactlyCountTimes(#drawCircle, 1));
  });

  testWidgets('Slider has correct animations when reparented', (WidgetTester tester) async {
    final Key sliderKey = GlobalKey(debugLabel: 'A');
    double value = 0.0;

    Widget buildSlider(int parents) {
      Widget createParents(int parents, StateSetter setState) {
        Widget slider = Slider(
          key: sliderKey,
          value: value,
          divisions: 4,
          onChanged: (double newValue) {
            setState(() {
              value = newValue;
            });
          },
        );

        for (int i = 0; i < parents; ++i) {
          slider = Column(children: <Widget>[slider]);
        }
        return slider;
      }

      return MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: createParents(parents, setState),
              );
            },
          ),
        ),
      );
    }

    Future<void> testReparenting(bool reparent) async {
      final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
      final Offset center = tester.getCenter(find.byType(Slider));
      // Move to 0.0.
      TestGesture gesture = await tester.startGesture(Offset.zero);
      await tester.pump();
      await gesture.up();
      await tester.pumpAndSettle();
      expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
      expect(
        material,
        paints
          ..circle(x: 26.0, y: 24.0, radius: 1.0)
          ..circle(x: 213.0, y: 24.0, radius: 1.0)
          ..circle(x: 400.0, y: 24.0, radius: 1.0)
          ..circle(x: 587.0, y: 24.0, radius: 1.0)
          ..circle(x: 774.0, y: 24.0, radius: 1.0)
          ..circle(x: 24.0, y: 24.0, radius: 10.0),
      );

      gesture = await tester.startGesture(center);
      await tester.pump();
      // Wait for animations to start.
      await tester.pump(const Duration(milliseconds: 25));
      expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
      expect(
        material,
        paints
          ..circle(x: 111.20703125, y: 24.0, radius: 5.687664985656738)
          ..circle(x: 26.0, y: 24.0, radius: 1.0)
          ..circle(x: 213.0, y: 24.0, radius: 1.0)
          ..circle(x: 400.0, y: 24.0, radius: 1.0)
          ..circle(x: 587.0, y: 24.0, radius: 1.0)
          ..circle(x: 774.0, y: 24.0, radius: 1.0)
          ..circle(x: 111.20703125, y: 24.0, radius: 10.0),
      );

      // Reparenting in the middle of an animation should do nothing.
      if (reparent) {
        await tester.pumpWidget(buildSlider(2));
      }

      // Move a little further in the animations.
      await tester.pump(const Duration(milliseconds: 10));
      expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
      expect(
        material,
        paints
          ..circle(x: 190.0135726928711, y: 24.0, radius: 12.0)
          ..circle(x: 26.0, y: 24.0, radius: 1.0)
          ..circle(x: 213.0, y: 24.0, radius: 1.0)
          ..circle(x: 400.0, y: 24.0, radius: 1.0)
          ..circle(x: 587.0, y: 24.0, radius: 1.0)
          ..circle(x: 774.0, y: 24.0, radius: 1.0)
          ..circle(x: 190.0135726928711, y: 24.0, radius: 10.0),
      );
      // Wait for animations to finish.
      await tester.pumpAndSettle();
      expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
      expect(
        material,
        paints
          ..circle(x: 400.0, y: 24.0, radius: 24.0)
          ..circle(x: 26.0, y: 24.0, radius: 1.0)
          ..circle(x: 213.0, y: 24.0, radius: 1.0)
          ..circle(x: 400.0, y: 24.0, radius: 1.0)
          ..circle(x: 587.0, y: 24.0, radius: 1.0)
          ..circle(x: 774.0, y: 24.0, radius: 1.0)
          ..circle(x: 400.0, y: 24.0, radius: 10.0),
      );
      await gesture.up();
      await tester.pumpAndSettle();
      expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
      expect(
        material,
        paints
          ..circle(x: 26.0, y: 24.0, radius: 1.0)
          ..circle(x: 213.0, y: 24.0, radius: 1.0)
          ..circle(x: 400.0, y: 24.0, radius: 1.0)
          ..circle(x: 587.0, y: 24.0, radius: 1.0)
          ..circle(x: 774.0, y: 24.0, radius: 1.0)
          ..circle(x: 400.0, y: 24.0, radius: 10.0),
      );
    }

    await tester.pumpWidget(buildSlider(1));
    // Do it once without reparenting in the middle of an animation
    await testReparenting(false);
    // Now do it again with reparenting in the middle of an animation.
    await testReparenting(true);
  });

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

    await tester.pumpWidget(MaterialApp(
      home: Directionality(
        textDirection: TextDirection.ltr,
        child: Material(
          child: Slider(
            value: 0.5,
            onChanged: (double v) { },
          ),
        ),
      ),
    ));

    await tester.pumpAndSettle();

    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics(
              id: 1,
              textDirection: TextDirection.ltr,
              children: <TestSemantics>[
                TestSemantics(
                  id: 2,
                  children: <TestSemantics>[
                    TestSemantics(
                      id: 3,
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          id: 4,
                          flags: <SemanticsFlag>[
                            SemanticsFlag.hasEnabledState,
                            SemanticsFlag.isEnabled,
                            SemanticsFlag.isFocusable,
                            SemanticsFlag.isSlider,
                          ],
                          actions: <SemanticsAction>[
                            SemanticsAction.increase,
                            SemanticsAction.decrease,
                          ],
                          value: '50%',
                          increasedValue: '55%',
                          decreasedValue: '45%',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );

    // Disable slider
    await tester.pumpWidget(const MaterialApp(
      home: Directionality(
        textDirection: TextDirection.ltr,
        child: Material(
          child: Slider(
            value: 0.5,
            onChanged: null,
          ),
        ),
      ),
    ));

    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics(
              id: 1,
              textDirection: TextDirection.ltr,
              children: <TestSemantics>[
                TestSemantics(
                  id: 2,
                  children: <TestSemantics>[
                    TestSemantics(
                      id: 3,
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          id: 4,
                          flags: <SemanticsFlag>[
                            SemanticsFlag.hasEnabledState,
                            // isFocusable is delayed by 1 frame.
                            SemanticsFlag.isFocusable,
                            SemanticsFlag.isSlider,
                          ],
                          value: '50%',
                          increasedValue: '55%',
                          decreasedValue: '45%',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );

    await tester.pump();
    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics(
              id: 1,
              textDirection: TextDirection.ltr,
              children: <TestSemantics>[
                TestSemantics(
                  id: 2,
                  children: <TestSemantics>[
                    TestSemantics(
                      id: 3,
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          id: 4,
                          flags: <SemanticsFlag>[
                            SemanticsFlag.hasEnabledState,
                            SemanticsFlag.isSlider,
                          ],
                          value: '50%',
                          increasedValue: '55%',
                          decreasedValue: '45%',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );

    semantics.dispose();
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android,  TargetPlatform.fuchsia, TargetPlatform.linux }));

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

    await tester.pumpWidget(
      MaterialApp(
        home: Theme(
          data: ThemeData.light(),
          child: Directionality(
            textDirection: TextDirection.ltr,
            child: Material(
              child: Slider(
                value: 100.0,
                max: 200.0,
                onChanged: (double v) { },
              ),
            ),
          ),
        ),
      ),
    );

    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics(
              id: 1,
              textDirection: TextDirection.ltr,
              children: <TestSemantics>[
                TestSemantics(
                  id: 2,
                  children: <TestSemantics>[
                    TestSemantics(
                      id: 3,
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          id: 4,
                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider],
                          actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
                          value: '50%',
                          increasedValue: '60%',
                          decreasedValue: '40%',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );

    // Disable slider
    await tester.pumpWidget(const MaterialApp(
      home: Directionality(
        textDirection: TextDirection.ltr,
        child: Material(
          child: Slider(
            value: 0.5,
            onChanged: null,
          ),
        ),
      ),
    ));

    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics(
              id: 1,
              textDirection: TextDirection.ltr,
              children: <TestSemantics>[
                TestSemantics(
                  id: 2,
                  children: <TestSemantics>[
                    TestSemantics(
                      id: 3,
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          id: 5,
                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isSlider],
                          value: '50%',
                          increasedValue: '60%',
                          decreasedValue: '40%',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );
    semantics.dispose();
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS,  TargetPlatform.macOS }));

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

    await tester.pumpWidget(MaterialApp(
      home: Directionality(
        textDirection: TextDirection.ltr,
        child: Material(
          child: Slider(
            value: 0.5,
            onChanged: (double v) { },
          ),
        ),
      ),
    ));

    await tester.pumpAndSettle();

    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics(
              id: 1,
              textDirection: TextDirection.ltr,
              children: <TestSemantics>[
                TestSemantics(
                  id: 2,
                  children: <TestSemantics>[
                    TestSemantics(
                      id: 3,
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          id: 4,
                          flags: <SemanticsFlag>[
                            SemanticsFlag.hasEnabledState,
                            SemanticsFlag.isEnabled,
                            SemanticsFlag.isFocusable,
                            SemanticsFlag.isSlider,
                          ],
                          actions: <SemanticsAction>[
                            SemanticsAction.increase,
                            SemanticsAction.decrease,
                            SemanticsAction.didGainAccessibilityFocus,
                          ],
                          value: '50%',
                          increasedValue: '55%',
                          decreasedValue: '45%',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );

    // Disable slider
    await tester.pumpWidget(const MaterialApp(
      home: Directionality(
        textDirection: TextDirection.ltr,
        child: Material(
          child: Slider(
            value: 0.5,
            onChanged: null,
          ),
        ),
      ),
    ));

    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics(
              id: 1,
              textDirection: TextDirection.ltr,
              children: <TestSemantics>[
                TestSemantics(
                  id: 2,
                  children: <TestSemantics>[
                    TestSemantics(
                      id: 3,
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          id: 4,
                          flags: <SemanticsFlag>[
                            SemanticsFlag.hasEnabledState,
                            // isFocusable is delayed by 1 frame.
                            SemanticsFlag.isFocusable,
                            SemanticsFlag.isSlider,
                          ],
                          actions: <SemanticsAction>[
                            SemanticsAction.didGainAccessibilityFocus,
                          ],
                          value: '50%',
                          increasedValue: '55%',
                          decreasedValue: '45%',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );

    await tester.pump();
    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics(
              id: 1,
              textDirection: TextDirection.ltr,
              children: <TestSemantics>[
                TestSemantics(
                  id: 2,
                  children: <TestSemantics>[
                    TestSemantics(
                      id: 3,
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          id: 4,
                          flags: <SemanticsFlag>[
                            SemanticsFlag.hasEnabledState,
                            SemanticsFlag.isSlider,
                          ],
                          actions: <SemanticsAction>[
                            SemanticsAction.didGainAccessibilityFocus,
                          ],
                          value: '50%',
                          increasedValue: '55%',
                          decreasedValue: '45%',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );

    semantics.dispose();
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.windows }));

  testWidgets('Slider semantics with custom formatter', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);

    await tester.pumpWidget(MaterialApp(
      home: Directionality(
        textDirection: TextDirection.ltr,
        child: Material(
          child: Slider(
            value: 40.0,
            max: 200.0,
            divisions: 10,
            semanticFormatterCallback: (double value) => value.round().toString(),
            onChanged: (double v) { },
          ),
        ),
      ),
    ));

    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics(
              id: 1,
              textDirection: TextDirection.ltr,
              children: <TestSemantics>[
                TestSemantics(
                  id: 2,
                  children: <TestSemantics>[
                    TestSemantics(
                      id: 3,
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          id: 4,
                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider],
                          actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
                          value: '40',
                          increasedValue: '60',
                          decreasedValue: '20',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );
    semantics.dispose();
  });

  // Regression test for https://github.com/flutter/flutter/issues/101868
  testWidgets('Slider.label info should not write to semantic node', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);

    await tester.pumpWidget(MaterialApp(
      home: Directionality(
        textDirection: TextDirection.ltr,
        child: Material(
          child: Slider(
            value: 40.0,
            max: 200.0,
            divisions: 10,
            semanticFormatterCallback: (double value) => value.round().toString(),
            onChanged: (double v) { },
            label: 'Bingo',
          ),
        ),
      ),
    ));

    expect(
      semantics,
      hasSemantics(
        TestSemantics.root(
          children: <TestSemantics>[
            TestSemantics(
              id: 1,
              textDirection: TextDirection.ltr,
              children: <TestSemantics>[
                TestSemantics(
                  id: 2,
                  children: <TestSemantics>[
                    TestSemantics(
                      id: 3,
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          id: 4,
                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider],
                          actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
                          value: '40',
                          increasedValue: '60',
                          decreasedValue: '20',
                          textDirection: TextDirection.ltr,
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreRect: true,
        ignoreTransform: true,
      ),
    );
    semantics.dispose();
  });

  testWidgets('Slider is focusable and has correct focus color', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
    addTearDown(focusNode.dispose);
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    final ThemeData theme = ThemeData(useMaterial3: true);
    double value = 0.5;
    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        theme: theme,
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                value: value,
                onChanged: enabled
                  ? (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    }
                  : null,
                autofocus: true,
                focusNode: focusNode,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    // Check that the overlay shows when focused.
    await tester.pumpAndSettle();
    expect(focusNode.hasPrimaryFocus, isTrue);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
    );

    // Check that the overlay does not show when unfocused and disabled.
    await tester.pumpWidget(buildApp(enabled: false));
    await tester.pumpAndSettle();
    expect(focusNode.hasPrimaryFocus, isFalse);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
    );
  });

  testWidgets('Slider has correct focus color from overlayColor property', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
    addTearDown(focusNode.dispose);
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    double value = 0.5;
    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                value: value,
                overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
                  if (states.contains(MaterialState.focused)) {
                    return Colors.purple[500]!;
                  }

                  return Colors.transparent;
                }),
                onChanged: enabled
                  ? (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    }
                  : null,
                autofocus: true,
                focusNode: focusNode,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    // Check that the overlay shows when focused.
    await tester.pumpAndSettle();
    expect(focusNode.hasPrimaryFocus, isTrue);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: Colors.purple[500]),
    );

    // Check that the overlay does not show when focused and disabled.
    await tester.pumpWidget(buildApp(enabled: false));
    await tester.pumpAndSettle();
    expect(focusNode.hasPrimaryFocus, isFalse);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: Colors.purple[500])),
    );
  });

  testWidgets('Slider can be hovered and has correct hover color', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    final ThemeData theme = ThemeData(useMaterial3: true);
    double value = 0.5;
    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        theme: theme,
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                value: value,
                onChanged: enabled
                  ? (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    }
                  : null,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    // Slider does not have overlay when enabled and not hovered.
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: Colors.orange[500])),
    );

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

    // Slider has overlay when enabled and hovered.
    await tester.pumpWidget(buildApp());
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: theme.colorScheme.primary.withOpacity(0.08)),
    );

    // Slider still shows correct hovered color after pressing/dragging
    await gesture.down(tester.getCenter(find.byType(Slider)));
    await tester.pump();
    await gesture.up();
    await tester.pumpAndSettle();
    await gesture.moveTo(const Offset(0.0, 100.0));
    await tester.pumpAndSettle();
    await gesture.moveTo(tester.getCenter(find.byType(Slider)));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: theme.colorScheme.primary.withOpacity(0.08)),
    );

    // Slider does not have an overlay when disabled and hovered.
    await tester.pumpWidget(buildApp(enabled: false));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: Colors.orange[500])),
    );
  });

  testWidgets('Slider has correct hovered color from overlayColor property', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    double value = 0.5;
    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                value: value,
                overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
                  if (states.contains(MaterialState.hovered)) {
                    return Colors.cyan[500]!;
                  }

                  return Colors.transparent;
                }),
                onChanged: enabled
                  ? (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    }
                  : null,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    // Slider does not have overlay when enabled and not hovered.
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: Colors.cyan[500])),
    );

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

    // Slider has overlay when enabled and hovered.
    await tester.pumpWidget(buildApp());
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: Colors.cyan[500]),
    );

    // Slider does not have an overlay when disabled and hovered.
    await tester.pumpWidget(buildApp(enabled: false));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: Colors.cyan[500])),
    );
  });

  testWidgets('Slider is draggable and has correct dragged color', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    double value = 0.5;
    final ThemeData theme = ThemeData(useMaterial3: true);
    final Key sliderKey = UniqueKey();
    final FocusNode focusNode = FocusNode();
    addTearDown(focusNode.dispose);

    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        theme: theme,
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                key: sliderKey,
                value: value,
                focusNode: focusNode,
                onChanged: enabled
                  ? (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    }
                  : null,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    // Slider does not have overlay when enabled and not dragged.
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
    );

    // Start dragging.
    final TestGesture drag = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
    await tester.pump(kPressTimeout);

    // Less than configured touch slop, more than default touch slop
    await drag.moveBy(const Offset(19.0, 0));
    await tester.pump();

    // Slider has overlay when enabled and dragged.
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
    );

    await drag.up();
    await tester.pumpAndSettle();

    // Slider without focus doesn't have overlay when enabled and dragged.
    expect(focusNode.hasFocus, false);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
    );

    // Slider has overlay when enabled, dragged and focused.
    focusNode.requestFocus();
    await tester.pumpAndSettle();

    expect(focusNode.hasFocus, true);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
    );
  });

  testWidgets('Slider has correct dragged color from overlayColor property', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    double value = 0.5;
    final Key sliderKey = UniqueKey();
    final FocusNode focusNode = FocusNode();
    addTearDown(focusNode.dispose);

    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                key: sliderKey,
                value: value,
                focusNode: focusNode,
                overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
                  if (states.contains(MaterialState.dragged)) {
                    return Colors.lime[500]!;
                  }

                  return Colors.transparent;
                }),
                onChanged: enabled
                  ? (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    }
                  : null,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    // Slider does not have overlay when enabled and not dragged.
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: Colors.lime[500])),
    );

    // Start dragging.
    final TestGesture drag = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
    await tester.pump(kPressTimeout);

    // Less than configured touch slop, more than default touch slop
    await drag.moveBy(const Offset(19.0, 0));
    await tester.pump();

    // Slider has overlay when enabled and dragged.
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: Colors.lime[500]),
    );

    await drag.up();
    await tester.pumpAndSettle();

    // Slider without focus doesn't have overlay when enabled and dragged.
    expect(focusNode.hasFocus, false);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: Colors.lime[500])),
    );
  });

  testWidgets('OverlayColor property is correctly applied when activeColor is also provided', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
    addTearDown(focusNode.dispose);
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    double value = 0.5;
    const Color activeColor = Color(0xffff0000);
    const Color overlayColor = Color(0xff0000ff);

    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                value: value,
                activeColor: activeColor,
                overlayColor: const MaterialStatePropertyAll<Color?>(overlayColor),
                onChanged: enabled
                  ? (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    }
                  : null,
                focusNode: focusNode,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());
    await tester.pumpAndSettle();

    final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
    // Check that thumb color is using active color.
    expect(material, paints..circle(color: activeColor));

    focusNode.requestFocus();
    await tester.pumpAndSettle();

    // Check that the overlay shows when focused.
    expect(focusNode.hasPrimaryFocus, isTrue);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: overlayColor),
    );

    // Check that the overlay does not show when focused and disabled.
    await tester.pumpWidget(buildApp(enabled: false));
    await tester.pumpAndSettle();
    expect(focusNode.hasPrimaryFocus, isFalse);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: overlayColor)),
    );
  });

  testWidgets('Slider can be incremented and decremented by keyboard shortcuts - LTR', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    double startValue = 0.0;
    double currentValue = 0.5;
    double endValue = 0.0;
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                value: currentValue,
                onChangeStart: (double newValue) {
                  setState(() {
                    startValue = newValue;
                  });
                },
                onChanged: (double newValue) {
                  setState(() {
                    currentValue = newValue;
                  });
                },
                onChangeEnd: (double newValue) {
                  setState(() {
                    endValue = newValue;
                  });
                },
                autofocus: true,
              );
            }),
          ),
        ),
      ),
    );
    await tester.pumpAndSettle();

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
    await tester.pumpAndSettle();
    expect(startValue, 0.5);
    expect(currentValue, 0.55);
    expect(endValue, 0.55);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
    await tester.pumpAndSettle();
    expect(startValue, 0.55);
    expect(currentValue, 0.5);
    expect(endValue, 0.5);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
    await tester.pumpAndSettle();
    expect(startValue, 0.5);
    expect(currentValue, 0.55);
    expect(endValue, 0.55);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
    await tester.pumpAndSettle();
    expect(startValue, 0.55);
    expect(currentValue, 0.5);
    expect(endValue, 0.5);
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android,  TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));

  testWidgets('Slider can be incremented and decremented by keyboard shortcuts - LTR', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    double startValue = 0.0;
    double currentValue = 0.5;
    double endValue = 0.0;
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                value: currentValue,
                onChangeStart: (double newValue) {
                  setState(() {
                    startValue = newValue;
                  });
                },
                onChanged: (double newValue) {
                  setState(() {
                    currentValue = newValue;
                  });
                },
                onChangeEnd: (double newValue) {
                  setState(() {
                    endValue = newValue;
                  });
                },
                autofocus: true,
              );
            }),
          ),
        ),
      ),
    );
    await tester.pumpAndSettle();

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
    await tester.pumpAndSettle();
    expect(startValue, 0.5);
    expect(currentValue, 0.6);
    expect(endValue, 0.6);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
    await tester.pumpAndSettle();
    expect(startValue, 0.6);
    expect(currentValue, 0.5);
    expect(endValue, 0.5);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
    await tester.pumpAndSettle();
    expect(startValue, 0.5);
    expect(currentValue, 0.6);
    expect(endValue, 0.6);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
    await tester.pumpAndSettle();
    expect(startValue, 0.6);
    expect(currentValue, 0.5);
    expect(endValue, 0.5);
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS,  TargetPlatform.macOS }));

  testWidgets('Slider can be incremented and decremented by keyboard shortcuts - RTL', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    double startValue = 0.0;
    double currentValue = 0.5;
    double endValue = 0.0;
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Directionality(
                textDirection: TextDirection.rtl,
                child: Slider(
                  value: currentValue,
                  onChangeStart: (double newValue) {
                    setState(() {
                      startValue = newValue;
                    });
                  },
                  onChanged: (double newValue) {
                    setState(() {
                      currentValue = newValue;
                    });
                  },
                  onChangeEnd: (double newValue) {
                    setState(() {
                      endValue = newValue;
                    });
                  },
                  autofocus: true,
                ),
              );
            }),
          ),
        ),
      ),
    );
    await tester.pumpAndSettle();

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
    await tester.pumpAndSettle();
    expect(startValue, 0.5);
    expect(currentValue, 0.45);
    expect(endValue, 0.45);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
    await tester.pumpAndSettle();
    expect(startValue, 0.45);
    expect(currentValue, 0.5);
    expect(endValue, 0.5);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
    await tester.pumpAndSettle();
    expect(startValue, 0.5);
    expect(currentValue, 0.55);
    expect(endValue, 0.55);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
    await tester.pumpAndSettle();
    expect(startValue, 0.55);
    expect(currentValue, 0.5);
    expect(endValue, 0.5);
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android,  TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));

  testWidgets('Slider can be incremented and decremented by keyboard shortcuts - RTL', (WidgetTester tester) async {
    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    double startValue = 0.0;
    double currentValue = 0.5;
    double endValue = 0.0;
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Directionality(
                textDirection: TextDirection.rtl,
                child: Slider(
                  value: currentValue,
                  onChangeStart: (double newValue) {
                    setState(() {
                      startValue = newValue;
                    });
                  },
                  onChanged: (double newValue) {
                    setState(() {
                      currentValue = newValue;
                    });
                  },
                  onChangeEnd: (double newValue) {
                    setState(() {
                      endValue = newValue;
                    });
                  },
                  autofocus: true,
                ),
              );
            }),
          ),
        ),
      ),
    );
    await tester.pumpAndSettle();

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
    await tester.pumpAndSettle();
    expect(startValue, 0.5);
    expect(currentValue, 0.4);
    expect(endValue, 0.4);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
    await tester.pumpAndSettle();
    expect(startValue, 0.4);
    expect(currentValue, 0.5);
    expect(endValue, 0.5);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
    await tester.pumpAndSettle();
    expect(startValue, 0.5);
    expect(currentValue, 0.6);
    expect(endValue, 0.6);

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
    await tester.pumpAndSettle();
    expect(startValue, 0.6);
    expect(currentValue, 0.5);
    expect(endValue, 0.5);
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS,  TargetPlatform.macOS }));

  testWidgets('In directional nav, Slider can be navigated out of by using up and down arrows', (WidgetTester tester) async {
    const Map<ShortcutActivator, Intent> shortcuts = <ShortcutActivator, Intent>{
      SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left),
      SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent(TraversalDirection.right),
      SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down),
      SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up),
    };

    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
    double topSliderValue = 0.5;
    double bottomSliderValue = 0.5;
    await tester.pumpWidget(
      MaterialApp(
        home: Shortcuts(
          shortcuts: shortcuts,
          child: Material(
            child: Center(
              child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
                return MediaQuery(
                  data: const MediaQueryData(navigationMode: NavigationMode.directional),
                  child: Column(
                    children: <Widget>[
                      Slider(
                        value: topSliderValue,
                        onChanged: (double newValue) {
                          setState(() {
                            topSliderValue = newValue;
                          });
                        },
                        autofocus: true,
                      ),
                      Slider(
                        value: bottomSliderValue,
                        onChanged: (double newValue) {
                          setState(() {
                            bottomSliderValue = newValue;
                          });
                        },
                      ),
                    ]
                  ),
                );
              }),
            ),
          ),
        ),
      ),
    );
    await tester.pumpAndSettle();

    // The top slider is auto-focused and can be adjusted with left and right arrow keys.
    await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
    await tester.pumpAndSettle();
    expect(topSliderValue, 0.55, reason: 'focused top Slider increased after first arrowRight');
    expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by first arrowRight');

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
    await tester.pumpAndSettle();
    expect(topSliderValue, 0.5, reason: 'focused top Slider decreased after first arrowLeft');
    expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by first arrowLeft');

    // Pressing the down-arrow key moves focus down to the bottom slider
    await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
    await tester.pumpAndSettle();
    expect(topSliderValue, 0.5, reason: 'arrowDown unfocuses top Slider, does not alter its value');
    expect(bottomSliderValue, 0.5, reason: 'arrowDown focuses bottom Slider, does not alter its value');

    // The bottom slider is now focused and can be adjusted with left and right arrow keys.
    await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
    await tester.pumpAndSettle();
    expect(topSliderValue, 0.5, reason: 'unfocused top Slider unaffected by second arrowRight');
    expect(bottomSliderValue, 0.55, reason: 'focused bottom Slider increased by second arrowRight');

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
    await tester.pumpAndSettle();
    expect(topSliderValue, 0.5, reason: 'unfocused top Slider unaffected by second arrowLeft');
    expect(bottomSliderValue, 0.5, reason: 'focused bottom Slider decreased by second arrowLeft');

    // Pressing the up-arrow key moves focus back up to the top slider
    await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
    await tester.pumpAndSettle();
    expect(topSliderValue, 0.5, reason: 'arrowUp focuses top Slider, does not alter its value');
    expect(bottomSliderValue, 0.5, reason: 'arrowUp unfocuses bottom Slider, does not alter its value');

    // The top slider is now focused again and can be adjusted with left and right arrow keys.
    await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
    await tester.pumpAndSettle();
    expect(topSliderValue, 0.55, reason: 'focused top Slider increased after third arrowRight');
    expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by third arrowRight');

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
    await tester.pumpAndSettle();
    expect(topSliderValue, 0.5, reason: 'focused top Slider decreased after third arrowRight');
    expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by third arrowRight');
  });

  testWidgets('Slider gains keyboard focus when it gains semantics focus on Windows', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);
    final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;
    final FocusNode focusNode = FocusNode();
    addTearDown(focusNode.dispose);
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: Slider(
            value: 0.5,
            onChanged: (double _) {},
            focusNode: focusNode,
          ),
        ),
      ),
    );

    expect(semantics, hasSemantics(
      TestSemantics.root(
        children: <TestSemantics>[
          TestSemantics(
            id: 1,
            textDirection: TextDirection.ltr,
            children: <TestSemantics>[
              TestSemantics(
                id: 2,
                children: <TestSemantics>[
                  TestSemantics(
                    id: 3,
                    flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                    children: <TestSemantics>[
                      TestSemantics(
                        id: 4,
                        flags: <SemanticsFlag>[
                          SemanticsFlag.hasEnabledState,
                          SemanticsFlag.isEnabled,
                          SemanticsFlag.isFocusable,
                          SemanticsFlag.isSlider,
                        ],
                        actions: <SemanticsAction>[
                          SemanticsAction.increase,
                          SemanticsAction.decrease,
                          SemanticsAction.didGainAccessibilityFocus,
                        ],
                        value: '50%',
                        increasedValue: '55%',
                        decreasedValue: '45%',
                        textDirection: TextDirection.ltr,
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
      ),
      ignoreRect: true,
      ignoreTransform: true,
    ));

    expect(focusNode.hasFocus, isFalse);
    semanticsOwner.performAction(4, SemanticsAction.didGainAccessibilityFocus);
    await tester.pumpAndSettle();
    expect(focusNode.hasFocus, isTrue);
    semantics.dispose();
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.windows }));

  testWidgets('Value indicator appears when it should', (WidgetTester tester) async {
    final ThemeData baseTheme = ThemeData(
      platform: TargetPlatform.android,
      primarySwatch: Colors.blue,
    );
    SliderThemeData theme = baseTheme.sliderTheme;
    double value = 0.45;
    Widget buildApp({ required SliderThemeData sliderTheme, int? divisions, bool enabled = true }) {
      final ValueChanged<double>? onChanged = enabled ? (double d) => value = d : null;
      return MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: Theme(
                data: baseTheme,
                child: SliderTheme(
                  data: sliderTheme,
                  child: Slider(
                    value: value,
                    label: '$value',
                    divisions: divisions,
                    onChanged: onChanged,
                  ),
                ),
              ),
            ),
          ),
        ),
      );
    }

    Future<void> expectValueIndicator({
      required bool isVisible,
      required SliderThemeData theme,
      int? divisions,
      bool enabled = true,
    }) async {
      // Discrete enabled widget.
      await tester.pumpWidget(buildApp(sliderTheme: theme, divisions: divisions, enabled: enabled));
      final Offset center = tester.getCenter(find.byType(Slider));
      final TestGesture gesture = await tester.startGesture(center);
      // Wait for value indicator animation to finish.
      await tester.pumpAndSettle();


      final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
      expect(
        valueIndicatorBox,
        isVisible
            ? (paints..path(color: theme.valueIndicatorColor)..paragraph())
            : isNot(paints..path(color: theme.valueIndicatorColor)..paragraph()),
      );
      await gesture.up();
    }

    // Default (showValueIndicator set to onlyForDiscrete).
    await expectValueIndicator(isVisible: true, theme: theme, divisions: 3);
    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3, enabled: false);
    await expectValueIndicator(isVisible: false, theme: theme);
    await expectValueIndicator(isVisible: false, theme: theme, enabled: false);

    // With showValueIndicator set to onlyForContinuous.
    theme = theme.copyWith(showValueIndicator: ShowValueIndicator.onlyForContinuous);
    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3);
    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3, enabled: false);
    await expectValueIndicator(isVisible: true, theme: theme);
    await expectValueIndicator(isVisible: false, theme: theme, enabled: false);

    // discrete enabled widget with showValueIndicator set to always.
    theme = theme.copyWith(showValueIndicator: ShowValueIndicator.always);
    await expectValueIndicator(isVisible: true, theme: theme, divisions: 3);
    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3, enabled: false);
    await expectValueIndicator(isVisible: true, theme: theme);
    await expectValueIndicator(isVisible: false, theme: theme, enabled: false);

    // discrete enabled widget with showValueIndicator set to never.
    theme = theme.copyWith(showValueIndicator: ShowValueIndicator.never);
    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3);
    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3, enabled: false);
    await expectValueIndicator(isVisible: false, theme: theme);
    await expectValueIndicator(isVisible: false, theme: theme, enabled: false);
  });

  testWidgets("Slider doesn't start any animations after dispose", (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.0;
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: Center(
                  child: Slider(
                    key: sliderKey,
                    value: value,
                    divisions: 4,
                    onChanged: (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
    await tester.pumpAndSettle();
    expect(value, equals(0.5));
    await gesture.moveBy(const Offset(-500.0, 0.0));
    await tester.pumpAndSettle();
    // Change the tree to dispose the original widget.
    await tester.pumpWidget(Container());
    expect(await tester.pumpAndSettle(), equals(1));
    await gesture.up();
  });

  testWidgets('Slider removes value indicator from overlay if Slider gets disposed without value indicator animation completing.', (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    const Color fillColor = Color(0xf55f5f5f);
    double value = 0.0;

    Widget buildApp({
      int? divisions,
      bool enabled = true,
    }) {
      return MaterialApp(
        theme: ThemeData(useMaterial3: false),
        home: Scaffold(
          body: Builder(
            // The builder is used to pass the context from the MaterialApp widget
            // to the [Navigator]. This context is required in order for the
            // Navigator to work.
            builder: (BuildContext context) {
              return Column(
                children: <Widget>[
                  Slider(
                    key: sliderKey,
                    max: 100.0,
                    divisions: divisions,
                    label: '${value.round()}',
                    value: value,
                    onChanged: (double newValue) {
                      value = newValue;
                    },
                  ),
                  ElevatedButton(
                    child: const Text('Next'),
                    onPressed: () {
                      Navigator.of(context).pushReplacement(
                        MaterialPageRoute<void>(
                          builder: (BuildContext context) {
                            return ElevatedButton(
                              child: const Text('Inner page'),
                              onPressed: () { Navigator.of(context).pop(); },
                            );
                          },
                        ),
                      );
                    },
                  ),
                ],
              );
            },
          ),
        ),
      );
    }

    await tester.pumpWidget(buildApp(divisions: 3));

    final RenderObject valueIndicatorBox = tester.renderObject(find.byType(Overlay));
    final Offset topRight = tester.getTopRight(find.byType(Slider)).translate(-24, 0);
    final TestGesture gesture = await tester.startGesture(topRight);
    // Wait for value indicator animation to finish.
    await tester.pumpAndSettle();

    expect(find.byType(Slider), isNotNull);
    expect(
      valueIndicatorBox,
      paints
        // Represents the raised button with text, next.
        ..path(color: Colors.black)
        ..paragraph()
        // Represents the Slider.
        ..path(color: fillColor)
        ..paragraph(),
    );

    expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 4));
    expect(valueIndicatorBox, paintsExactlyCountTimes(#drawParagraph, 2));

    await tester.tap(find.text('Next'));
    await tester.pumpAndSettle();

    expect(find.byType(Slider), findsNothing);
    expect(
      valueIndicatorBox,
      isNot(
        paints
          ..path(color: fillColor)
          ..paragraph(),
      ),
    );

    // Represents the ElevatedButton with inner Text, inner page.
    expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 2));
    expect(valueIndicatorBox, paintsExactlyCountTimes(#drawParagraph, 1));

    // Don't stop holding the value indicator.
    await gesture.up();
    await tester.pumpAndSettle();
  });

  testWidgets('Slider.adaptive', (WidgetTester tester) async {
    double value = 0.5;

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

    for (final TargetPlatform platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
      value = 0.5;
      await tester.pumpWidget(buildFrame(platform));
      expect(find.byType(Slider), findsOneWidget);
      expect(find.byType(CupertinoSlider), findsOneWidget);

      expect(value, 0.5, reason: 'on ${platform.name}');
      final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CupertinoSlider)));
      // Drag to the right end of the track.
      await gesture.moveBy(const Offset(600.0, 0.0));
      expect(value, 1.0, reason: 'on ${platform.name}');
      await gesture.up();
    }

    for (final TargetPlatform platform in <TargetPlatform>[TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows]) {
      value = 0.5;
      await tester.pumpWidget(buildFrame(platform));
      await tester.pumpAndSettle(); // Finish the theme change animation.
      expect(find.byType(Slider), findsOneWidget);
      expect(find.byType(CupertinoSlider), findsNothing);

      expect(value, 0.5, reason: 'on ${platform.name}');
      final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Slider)));
      // Drag to the right end of the track.
      await gesture.moveBy(const Offset(600.0, 0.0));
      expect(value, 1.0, reason: 'on ${platform.name}');
      await gesture.up();
    }
  });

  testWidgets('Slider respects height from theme', (WidgetTester tester) async {
    final Key sliderKey = UniqueKey();
    double value = 0.0;
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              final SliderThemeData sliderTheme = SliderTheme.of(context).copyWith(tickMarkShape: TallSliderTickMarkShape());
              return Material(
                child: Center(
                  child: IntrinsicHeight(
                    child: SliderTheme(
                      data: sliderTheme,
                      child: Slider(
                        key: sliderKey,
                        value: value,
                        divisions: 4,
                        onChanged: (double newValue) {
                          setState(() {
                            value = newValue;
                          });
                        },
                      ),
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    final RenderBox renderObject = tester.renderObject<RenderBox>(find.byType(Slider));
    expect(renderObject.size.height, 200);
  });

  testWidgets('Slider changes mouse cursor when hovered', (WidgetTester tester) async {
    // Test Slider() constructor
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: MouseRegion(
                cursor: SystemMouseCursors.forbidden,
                child: Slider(
                  mouseCursor: SystemMouseCursors.text,
                  value: 0.5,
                  onChanged: (double newValue) { },
                ),
              ),
            ),
          ),
        ),
      ),
    );

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

    await tester.pump();

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

    // Test Slider.adaptive() constructor
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: MouseRegion(
                cursor: SystemMouseCursors.forbidden,
                child: Slider.adaptive(
                  mouseCursor: SystemMouseCursors.text,
                  value: 0.5,
                  onChanged: (double newValue) { },
                ),
              ),
            ),
          ),
        ),
      ),
    );

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

    // Test default cursor
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: MouseRegion(
                cursor: SystemMouseCursors.forbidden,
                child: Slider(
                  value: 0.5,
                  onChanged: (double newValue) { },
                ),
              ),
            ),
          ),
        ),
      ),
    );

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

  testWidgets('Slider MaterialStateMouseCursor resolves correctly', (WidgetTester tester) async {
    const MouseCursor disabledCursor = SystemMouseCursors.basic;
    const MouseCursor hoveredCursor = SystemMouseCursors.grab;
    const MouseCursor draggedCursor = SystemMouseCursors.move;

    Widget buildFrame({ required bool enabled }) {
      return MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: MouseRegion(
                cursor: SystemMouseCursors.forbidden,
                child: Slider(
                  mouseCursor: const _StateDependentMouseCursor(
                    disabled: disabledCursor,
                    hovered: hoveredCursor,
                    dragged: draggedCursor,
                  ),
                  value: 0.5,
                  onChanged: enabled ? (double newValue) { } : null,
                ),
              ),
            ),
          ),
        ),
      );
    }

    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
    await gesture.addPointer(location: Offset.zero);

    await tester.pumpWidget(buildFrame(enabled: false));
    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), disabledCursor);

    await tester.pumpWidget(buildFrame(enabled: true));
    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.none);

    await gesture.moveTo(tester.getCenter(find.byType(Slider))); // start hover
    await tester.pumpAndSettle();
    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), hoveredCursor);

    await tester.timedDrag(
      find.byType(Slider),
      const Offset(20.0, 0.0),
      const Duration(milliseconds: 100),
    );
    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.move);
  });

  testWidgets('Slider implements debugFillProperties', (WidgetTester tester) async {
    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

    const Slider(
      activeColor: Colors.blue,
      divisions: 10,
      inactiveColor: Colors.grey,
      secondaryActiveColor: Colors.blueGrey,
      label: 'Set a value',
      max: 100.0,
      onChanged: null,
      value: 50.0,
      secondaryTrackValue: 75.0,
    ).debugFillProperties(builder);

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

    expect(description, <String>[
      'value: 50.0',
      'secondaryTrackValue: 75.0',
      'disabled',
      'min: 0.0',
      'max: 100.0',
      'divisions: 10',
      'label: "Set a value"',
      'activeColor: MaterialColor(primary value: Color(0xff2196f3))',
      'inactiveColor: MaterialColor(primary value: Color(0xff9e9e9e))',
      'secondaryActiveColor: MaterialColor(primary value: Color(0xff607d8b))',
    ]);
  });

  testWidgets('Slider track paints correctly when the shape is rectangular', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(
          sliderTheme: const SliderThemeData(
            trackShape: RectangularSliderTrackShape(),
          ),
        ),
        home: const Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: Slider(
                value: 0.5,
                onChanged: null,
              ),
            ),
          ),
        ),
      ),
    );

    // _RenderSlider is the last render object in the tree.
    final RenderObject renderObject = tester.allRenderObjects.last;

    // The active track rect should start at 24.0 pixels,
    // and there should not have a gap between active and inactive track.
    expect(
      renderObject,
      paints
        ..rect(rect: const Rect.fromLTRB(24.0, 298.0, 400.0, 302.0)) // active track Rect.
        ..rect(rect: const Rect.fromLTRB(400.0, 298.0, 776.0, 302.0)), // inactive track Rect.
    );
  });

  testWidgets('SliderTheme change should trigger re-layout', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/118955
    double sliderValue = 0.0;
    Widget buildFrame(ThemeMode themeMode) {
      return MaterialApp(
        themeMode: themeMode,
        theme: ThemeData(brightness: Brightness.light, useMaterial3: true),
        darkTheme: ThemeData(brightness: Brightness.dark, useMaterial3: true),
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: SizedBox(
                height: 10.0,
                width: 10.0,
                child: Slider(
                  value: sliderValue,
                  label: 'label',
                  onChanged: (double value) => sliderValue = value,
                ),
              ),
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildFrame(ThemeMode.light));

    // _RenderSlider is the last render object in the tree.
    final RenderObject renderObject = tester.allRenderObjects.last;
    expect(renderObject.debugNeedsLayout, false);

    await tester.pumpWidget(buildFrame(ThemeMode.dark));
    await tester.pump(
      const Duration(milliseconds: 100), // to let the theme animate
      EnginePhase.build,
    );

    expect(renderObject.debugNeedsLayout, true);

    // Pump the rest of the frames to complete the test.
    await tester.pumpAndSettle();
  });

  testWidgets('Slider can be painted in a narrower constraint', (WidgetTester tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: Material(
            child: Center(
              child: SizedBox(
                height: 10.0,
                width: 10.0,
                child: Slider(
                  value: 0.5,
                  onChanged: null,
                ),
              ),
            ),
          ),
        ),
      ),
    );

    // _RenderSlider is the last render object in the tree.
    final RenderObject renderObject = tester.allRenderObjects.last;

    expect(
      renderObject,
      paints
        // active track RRect
        ..rrect(rrect: RRect.fromLTRBAndCorners(-14.0, 2.0, 5.0, 8.0, topLeft: const Radius.circular(3.0), bottomLeft: const Radius.circular(3.0)))
        // inactive track RRect
        ..rrect(rrect: RRect.fromLTRBAndCorners(5.0, 3.0, 24.0, 7.0, topRight: const Radius.circular(2.0), bottomRight: const Radius.circular(2.0)))
        // thumb
        ..circle(x: 5.0, y: 5.0, radius: 10.0, ),
    );
  });

  testWidgets('Update the divisions and value at the same time for Slider', (WidgetTester tester) async {
    // Regress test for https://github.com/flutter/flutter/issues/65943
    Widget buildFrame(double maxValue) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: Slider.adaptive(
              value: 5,
              max: maxValue,
              divisions: maxValue.toInt(),
              onChanged: (double newValue) {},
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildFrame(10));

    // _RenderSlider is the last render object in the tree.
    final RenderObject renderObject = tester.allRenderObjects.last;

    // Update the divisions from 10 to 15, the thumb should be paint at the correct position.
    await tester.pumpWidget(buildFrame(15));
    await tester.pumpAndSettle(); // Finish the animation.

    late RRect activeTrackRRect;
    expect(renderObject, paints..something((Symbol method, List<dynamic> arguments) {
      if (method != #drawRRect) {
        return false;
      }
      activeTrackRRect = arguments[0] as RRect;
      return true;
    }));

    // The thumb should at one-third(5 / 15) of the Slider.
    // The right of the active track shape is the position of the thumb.
    // 24.0 is the default margin, (800.0 - 24.0 - 24.0) is the slider's width.
    expect(nearEqual(activeTrackRRect.right, (800.0 - 24.0 - 24.0) * (5 / 15) + 24.0, 0.01), true);
  });

  testWidgets('Slider paints thumbColor', (WidgetTester tester) async {
    const Color color = Color(0xffffc107);

    final Widget sliderAdaptive = MaterialApp(
      theme: ThemeData(platform: TargetPlatform.iOS),
      home: Material(
        child: Slider(
          value: 0,
          onChanged: (double newValue) {},
          thumbColor: color,
        ),
      ),
    );

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

    final MaterialInkController material =
        Material.of(tester.element(find.byType(Slider)));
    expect(material, paints..circle(color: color));
  });

  testWidgets('Slider.adaptive paints thumbColor on Android',
      (WidgetTester tester) async {
    const Color color = Color(0xffffc107);

    final Widget sliderAdaptive = MaterialApp(
      theme: ThemeData(platform: TargetPlatform.android),
      home: Material(
        child: Slider.adaptive(
          value: 0,
          onChanged: (double newValue) {},
          thumbColor: color,
        ),
      ),
    );

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

    final MaterialInkController material =
        Material.of(tester.element(find.byType(Slider)));
    expect(material, paints..circle(color: color));
  });

  testWidgets('If thumbColor is null, it defaults to CupertinoColors.white',
      (WidgetTester tester) async {
    final Widget sliderAdaptive = MaterialApp(
      theme: ThemeData(platform: TargetPlatform.iOS),
      home: Material(
        child: Slider.adaptive(
          value: 0,
          onChanged: (double newValue) {},
        ),
      ),
    );

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

    final MaterialInkController material =
        Material.of(tester.element(find.byType(CupertinoSlider)));
    expect(
      material,
      paints
        ..rrect()
        ..rrect()
        ..rrect()
        ..rrect()
        ..rrect()
        ..rrect(color: CupertinoColors.white),
    );
  });

  testWidgets('Slider.adaptive passes thumbColor to CupertinoSlider',
      (WidgetTester tester) async {
    const Color color = Color(0xffffc107);

    final Widget sliderAdaptive = MaterialApp(
      theme: ThemeData(platform: TargetPlatform.iOS),
      home: Material(
        child: Slider.adaptive(
          value: 0,
          onChanged: (double newValue) {},
          thumbColor: color,
        ),
      ),
    );

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

    final MaterialInkController material =
        Material.of(tester.element(find.byType(CupertinoSlider)));
    expect(
      material,
      paints..rrect()..rrect()..rrect()..rrect()..rrect()..rrect(color: color),
    );
  });

  // Regression test for https://github.com/flutter/flutter/issues/103566
  testWidgets('Drag gesture uses provided gesture settings', (WidgetTester tester) async {
    double value = 0.5;
    bool dragStarted = false;
    final Key sliderKey = UniqueKey();

    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: Center(
                  child: GestureDetector(
                    behavior: HitTestBehavior.deferToChild,
                    onHorizontalDragStart: (DragStartDetails details) {
                      dragStarted = true;
                    },
                    child: MediaQuery(
                      data: MediaQuery.of(context).copyWith(gestureSettings: const DeviceGestureSettings(touchSlop: 20)),
                      child: Slider(
                        value: value,
                        key: sliderKey,
                        onChanged: (double newValue) {
                          setState(() {
                            value = newValue;
                          });
                        },
                      ),
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    TestGesture drag = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
    await tester.pump(kPressTimeout);

    // Less than configured touch slop, more than default touch slop
    await drag.moveBy(const Offset(19.0, 0));
    await tester.pump();

    expect(value, 0.5);
    expect(dragStarted, true);

    dragStarted = false;

    await drag.up();
    await tester.pumpAndSettle();

    drag = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
    await tester.pump(kPressTimeout);

    bool sliderEnd = false;

    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: Center(
                  child: GestureDetector(
                    behavior: HitTestBehavior.deferToChild,
                    onHorizontalDragStart: (DragStartDetails details) {
                      dragStarted = true;
                    },
                    child: MediaQuery(
                      data: MediaQuery.of(context).copyWith(gestureSettings: const DeviceGestureSettings(touchSlop: 10)),
                      child: Slider(
                        value: value,
                        key: sliderKey,
                        onChanged: (double newValue) {
                          setState(() {
                            value = newValue;
                          });
                        },
                        onChangeEnd: (double endValue) {
                          sliderEnd = true;
                        },
                      ),
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    // More than touch slop.
    await drag.moveBy(const Offset(12.0, 0));

    await drag.up();
    await tester.pumpAndSettle();

    expect(sliderEnd, true);
    expect(dragStarted, false);
  });

  // Regression test for https://github.com/flutter/flutter/issues/139281
  testWidgets('Slider does not request focus when the value is changed', (WidgetTester tester) async {
    double value = 0.5;
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: Center(
          child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
            return Slider(
                value: value,
                onChanged: (double newValue) {
                  setState(() {
                    value = newValue;
                  });
                }
            );
          }),
        ),
      ),
    ));
    // Initially, the slider does not have focus whe enabled and not tapped.
    await tester.pumpAndSettle();
    expect(value, equals(0.5));
    // Get FocusNode from the state of the slider to include auto-generated FocusNode.
    // ignore: invalid_assignment
    final FocusNode focusNode = (tester.firstState(find.byType(Slider)) as dynamic).focusNode;
    // The slider does not have focus.
    expect(focusNode.hasFocus, false);
    final Offset sliderCenter = tester.getCenter(find.byType(Slider));
    final Offset tapLocation = Offset(sliderCenter.dx + 50, sliderCenter.dy);
    // Tap on the slider to change the value.
    final TestGesture gesture = await tester.createGesture();
    await gesture.addPointer();
    await gesture.down(tapLocation);
    await gesture.up();
    await tester.pumpAndSettle();
    expect(value, isNot(equals(0.5)));
    // The slider does not have focus after the value is changed.
    expect(focusNode.hasFocus, false);
  });

  // Regression test for https://github.com/flutter/flutter/issues/139281
  testWidgets('Overlay remains when Slider thumb is interacted', (WidgetTester tester) async {
    double value = 0.5;
    const Color overlayColor = Color(0xffff0000);
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: Center(
          child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
            return Slider(
                value: value,
                overlayColor: const MaterialStatePropertyAll<Color?>(overlayColor),
                onChanged: (double newValue) {
                  setState(() {
                    value = newValue;
                  });
                }
            );
          }),
        ),
      ),
    ));
    // Slider does not have overlay when enabled and not tapped.
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: overlayColor)),
    );
    final Offset sliderCenter = tester.getCenter(find.byType(Slider));
    // Tap and hold down on the thumb to keep it active.
    final TestGesture gesture = await tester.createGesture();
    await gesture.addPointer();
    await gesture.down(sliderCenter);
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: overlayColor),
    );
    // Hover on the slider but outside the thumb.
    await gesture.moveTo(tester.getTopLeft(find.byType(Slider)));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: overlayColor),
    );
    // Tap up on the slider.
    await gesture.up();
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: overlayColor)),
    );
  });

  testWidgets('Overlay appear only when hovered on the thumb on desktop', (WidgetTester tester) async {
    double value = 0.5;
    const Color overlayColor = Color(0xffff0000);

    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                value: value,
                overlayColor: const MaterialStatePropertyAll<Color?>(overlayColor),
                onChanged: enabled
                  ? (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    }
                  : null,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    // Slider does not have overlay when enabled and not hovered.
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: overlayColor)),
    );

    // Hover on the slider but outside the thumb.
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.addPointer();
    await gesture.moveTo(tester.getTopLeft(find.byType(Slider)));

    await tester.pumpWidget(buildApp());
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: overlayColor)),
    );

    // Hover on the thumb.
    await gesture.moveTo(tester.getCenter(find.byType(Slider)));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: overlayColor),
    );

    // Hover on the slider but outside the thumb.
    await gesture.moveTo(tester.getBottomRight(find.byType(Slider)));
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: overlayColor)),
    );
  }, variant: TargetPlatformVariant.desktop());

  testWidgets('Overlay remains when Slider is in focus on desktop', (WidgetTester tester) async {
    double value = 0.5;
    const Color overlayColor = Color(0xffff0000);
    final FocusNode focusNode = FocusNode();
    addTearDown(focusNode.dispose);

    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                value: value,
                focusNode: focusNode,
                overlayColor: const MaterialStatePropertyAll<Color?>(overlayColor),
                onChanged: enabled
                  ? (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    }
                  : null,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    // Slider does not have overlay when enabled and not tapped.
    await tester.pumpAndSettle();
    expect(focusNode.hasFocus, false);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: overlayColor)),
    );

    final Offset sliderCenter = tester.getCenter(find.byType(Slider));
    Offset tapLocation = Offset(sliderCenter.dx + 50, sliderCenter.dy);

    // Tap somewhere to bring overlay.
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.addPointer();
    await gesture.down(tapLocation);
    await gesture.up();
    focusNode.requestFocus();
    await tester.pumpAndSettle();
    expect(focusNode.hasFocus, true);
    expect(
      Material.of(tester.element(find.byType(Slider))),
      paints..circle(color: overlayColor),
    );

    tapLocation = Offset(sliderCenter.dx - 50, sliderCenter.dy);
    await gesture.down(tapLocation);
    await gesture.up();
    await tester.pumpAndSettle();
    expect(focusNode.hasFocus, true);
    // Overlay is removed when adjusted with a tap.
    expect(
      Material.of(tester.element(find.byType(Slider))),
      isNot(paints..circle(color: overlayColor)),
    );
  }, variant: TargetPlatformVariant.desktop());

  // Regression test for https://github.com/flutter/flutter/issues/123313, which only occurs on desktop platforms.
  testWidgets('Value indicator disappears after adjusting the slider on desktop', (WidgetTester tester) async {
    final ThemeData theme = ThemeData(useMaterial3: true);
    const double currentValue = 0.5;
    await tester.pumpWidget(MaterialApp(
      theme: theme,
      home: Material(
        child: Center(
          child: Slider(
            value: currentValue,
            divisions: 5,
            label: currentValue.toStringAsFixed(1),
            onChanged: (_) {},
          ),
        ),
      ),
    ));

    // Slider does not show value indicator initially.
    await tester.pumpAndSettle();
    RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
    expect(
      valueIndicatorBox,
      isNot(paints..scale()..path(color: theme.colorScheme.primary)),
    );

    final Offset sliderCenter = tester.getCenter(find.byType(Slider));
    final Offset tapLocation = Offset(sliderCenter.dx + 50, sliderCenter.dy);

    // Tap the slider by mouse to bring up the value indicator.
    await tester.tapAt(tapLocation, kind: PointerDeviceKind.mouse);
    await tester.pumpAndSettle();

    // Value indicator is visible.
    valueIndicatorBox = tester.renderObject(find.byType(Overlay));
    expect(
      valueIndicatorBox,
      paints..scale()..path(color: theme.colorScheme.primary),
    );

    // Wait for the value indicator to disappear.
    await tester.pumpAndSettle(const Duration(seconds: 2));

    // Value indicator is no longer visible.
    expect(
      valueIndicatorBox,
      isNot(paints..scale()..path(color: theme.colorScheme.primary)),
    );
  }, variant: TargetPlatformVariant.desktop());

  testWidgets('Value indicator remains when Slider is in focus on desktop', (WidgetTester tester) async {
    double value = 0.5;
    final FocusNode focusNode = FocusNode();
    addTearDown(focusNode.dispose);

    Widget buildApp({bool enabled = true}) {
      return MaterialApp(
        theme: ThemeData(
          sliderTheme: const SliderThemeData(
            showValueIndicator: ShowValueIndicator.always,
          ),
        ),
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
              return Slider(
                value: value,
                focusNode: focusNode,
                divisions: 5,
                label: value.toStringAsFixed(1),
                onChanged: enabled
                  ? (double newValue) {
                      setState(() {
                        value = newValue;
                      });
                    }
                  : null,
              );
            }),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildApp());

    // Slider does not show value indicator without focus.
    await tester.pumpAndSettle();
    expect(focusNode.hasFocus, false);
    RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
    expect(
      valueIndicatorBox,
      isNot(paints..path(color: const Color(0xff000000))..paragraph()),
    );

    final Offset sliderCenter = tester.getCenter(find.byType(Slider));
    final Offset tapLocation = Offset(sliderCenter.dx + 50, sliderCenter.dy);

    // Tap somewhere to bring value indicator.
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.addPointer();
    await gesture.down(tapLocation);
    await gesture.up();
    focusNode.requestFocus();
    await tester.pumpAndSettle();
    expect(focusNode.hasFocus, true);
    valueIndicatorBox = tester.renderObject(find.byType(Overlay));
    expect(
      valueIndicatorBox,
      paints..path(color: const Color(0xff000000))..paragraph(),
    );

    focusNode.unfocus();
    await tester.pumpAndSettle();
    expect(focusNode.hasFocus, false);
    expect(
      valueIndicatorBox,
      isNot(paints..path(color: const Color(0xff000000))..paragraph()),
    );
  }, variant: TargetPlatformVariant.desktop());

  testWidgets('Event on Slider should perform no-op if already unmounted', (WidgetTester tester) async {
    // Test covering crashing found in Google internal issue b/192329942.
    double value = 0.0;
    final ValueNotifier<bool> shouldShowSliderListenable =
        ValueNotifier<bool>(true);
    addTearDown(shouldShowSliderListenable.dispose);

    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: StatefulBuilder(
            builder: (BuildContext context, StateSetter setState) {
              return Material(
                child: Center(
                  child: ValueListenableBuilder<bool>(
                    valueListenable: shouldShowSliderListenable,
                    builder: (BuildContext context, bool shouldShowSlider, _) {
                      return GestureDetector(
                        behavior: HitTestBehavior.translucent,
                        // Note: it is important that `onTap` is non-null so
                        // [GestureDetector] will register tap events.
                        onTap: () {},
                        child: shouldShowSlider
                            ? Slider(
                                value: value,
                                onChanged: (double newValue) {
                                  setState(() {
                                    value = newValue;
                                  });
                                },
                              )
                            : const SizedBox.expand(),
                      );
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    // Move Slider.
    final TestGesture gesture = await tester
        .startGesture(tester.getRect(find.byType(Slider)).center);
    await gesture.moveBy(const Offset(1.0, 0.0));
    await tester.pumpAndSettle();

    // Hide Slider. Slider will dispose and unmount.
    shouldShowSliderListenable.value = false;
    await tester.pumpAndSettle();

    // Move Slider after unmounted.
    await gesture.moveBy(const Offset(1.0, 0.0));
    await tester.pumpAndSettle();

    expect(tester.takeException(), null);
  });

  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('Slider can be hovered and has correct hover color', (WidgetTester tester) async {
      tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
      final ThemeData theme = ThemeData(useMaterial3: false);
      double value = 0.5;
      Widget buildApp({bool enabled = true}) {
        return MaterialApp(
          theme: theme,
          home: Material(
            child: Center(
              child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
                return Slider(
                  value: value,
                  onChanged: enabled
                    ? (double newValue) {
                        setState(() {
                          value = newValue;
                        });
                      }
                    : null,
                );
              }),
            ),
          ),
        );
      }
      await tester.pumpWidget(buildApp());

      // Slider does not have overlay when enabled and not hovered.
      await tester.pumpAndSettle();
      expect(
        Material.of(tester.element(find.byType(Slider))),
        isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
      );

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

      // Slider has overlay when enabled and hovered.
      await tester.pumpWidget(buildApp());
      await tester.pumpAndSettle();
      expect(
        Material.of(tester.element(find.byType(Slider))),
        paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
      );

      // Slider does not have an overlay when disabled and hovered.
      await tester.pumpWidget(buildApp(enabled: false));
      await tester.pumpAndSettle();
      expect(
        Material.of(tester.element(find.byType(Slider))),
        isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
      );
    });

    testWidgets('Slider is focusable and has correct focus color', (WidgetTester tester) async {
      final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
      addTearDown(focusNode.dispose);
      tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
      final ThemeData theme = ThemeData();
      double value = 0.5;
      Widget buildApp({bool enabled = true}) {
        return MaterialApp(
          home: Material(
            child: Center(
              child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
                return Slider(
                  value: value,
                  onChanged: enabled
                    ? (double newValue) {
                        setState(() {
                          value = newValue;
                        });
                      }
                    : null,
                  autofocus: true,
                  focusNode: focusNode,
                );
              }),
            ),
          ),
        );
      }
      await tester.pumpWidget(buildApp());

      // Check that the overlay shows when focused.
      await tester.pumpAndSettle();
      expect(focusNode.hasPrimaryFocus, isTrue);
      expect(
        Material.of(tester.element(find.byType(Slider))),
        paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
      );

      // Check that the overlay does not show when unfocused and disabled.
      await tester.pumpWidget(buildApp(enabled: false));
      await tester.pumpAndSettle();
      expect(focusNode.hasPrimaryFocus, isFalse);
      expect(
        Material.of(tester.element(find.byType(Slider))),
        isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
      );
    });

    testWidgets('Slider is draggable and has correct dragged color', (WidgetTester tester) async {
      tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
      double value = 0.5;
      final ThemeData theme = ThemeData();
      final Key sliderKey = UniqueKey();
      final FocusNode focusNode = FocusNode();
      addTearDown(focusNode.dispose);

      Widget buildApp({bool enabled = true}) {
        return MaterialApp(
          home: Material(
            child: Center(
              child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
                return Slider(
                  key: sliderKey,
                  value: value,
                  focusNode: focusNode,
                  onChanged: enabled
                    ? (double newValue) {
                        setState(() {
                          value = newValue;
                        });
                      }
                    : null,
                );
              }),
            ),
          ),
        );
      }
      await tester.pumpWidget(buildApp());

      // Slider does not have overlay when enabled and not dragged.
      await tester.pumpAndSettle();
      expect(
        Material.of(tester.element(find.byType(Slider))),
        isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
      );

      // Start dragging.
      final TestGesture drag = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
      await tester.pump(kPressTimeout);

      // Less than configured touch slop, more than default touch slop
      await drag.moveBy(const Offset(19.0, 0));
      await tester.pump();

      // Slider has overlay when enabled and dragged.
      expect(
        Material.of(tester.element(find.byType(Slider))),
        paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
      );

      await drag.up();
      await tester.pumpAndSettle();

      // Slider without focus doesn't have overlay when enabled and dragged.
      expect(focusNode.hasFocus, false);
      expect(
        Material.of(tester.element(find.byType(Slider))),
        isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
      );
    });
  });

  group('Slider.allowedInteraction', () {
    testWidgets('SliderInteraction.tapOnly', (WidgetTester tester) async {
      double value = 1.0;
      final Key sliderKey = UniqueKey();
      // (slider's left padding (overlayRadius), windowHeight / 2)
      const Offset startOfTheSliderTrack = Offset(24, 300);
      const Offset centerOfTheSlideTrack = Offset(400, 300);
      final List<String> logs = <String>[];

      Widget buildWidget() => MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext _, StateSetter setState) {
              return Slider(
                value: value,
                key: sliderKey,
                allowedInteraction: SliderInteraction.tapOnly,
                onChangeStart: (double newValue) {
                  logs.add('onChangeStart');
                },
                onChanged: (double newValue) {
                  logs.add('onChanged');
                  setState(() {
                    value = newValue;
                  });
                },
                onChangeEnd: (double newValue) {
                  logs.add('onChangeEnd');
                },
              );
            }),
          ),
        ),
      );

      // allow tap only
      await tester.pumpWidget(buildWidget());

      expect(logs, isEmpty);

      // test tap
      final TestGesture gesture = await tester.startGesture(centerOfTheSlideTrack);
      await tester.pump();
      // changes from 1.0 -> 0.5
      expect(value, 0.5);
      expect(logs, <String>['onChangeStart', 'onChanged']);

      // test slide
      await gesture.moveTo(startOfTheSliderTrack);
      await tester.pump();
      // has no effect, remains 0.5
      expect(value, 0.5);
      expect(logs, <String>['onChangeStart', 'onChanged']);

      await gesture.up();
      await tester.pump();
      expect(logs, <String>['onChangeStart', 'onChanged', 'onChangeEnd']);
    });

    testWidgets('SliderInteraction.tapAndSlide (default)', (WidgetTester tester) async {
      double value = 1.0;
      final Key sliderKey = UniqueKey();
      // (slider's left padding (overlayRadius), windowHeight / 2)
      const Offset startOfTheSliderTrack = Offset(24, 300);
      const Offset centerOfTheSlideTrack = Offset(400, 300);
      const Offset endOfTheSliderTrack = Offset(800 - 24, 300);
      final List<String> logs = <String>[];

      Widget buildWidget() => MaterialApp(
        home: Material(
          child: Center(
            child: StatefulBuilder(builder: (BuildContext _, StateSetter setState) {
              return Slider(
                value: value,
                key: sliderKey,
                onChangeStart: (double newValue) {
                  logs.add('onChangeStart');
                },
                onChanged: (double newValue) {
                  logs.add('onChanged');
                  setState(() {
                    value = newValue;
                  });
                },
                onChangeEnd: (double newValue) {
                  logs.add('onChangeEnd');
                },
              );
            }),
          ),
        ),
      );

      await tester.pumpWidget(buildWidget());

      expect(logs, isEmpty);

      // Test tap.
      final TestGesture gesture = await tester.startGesture(centerOfTheSlideTrack);
      await tester.pump();
      // changes from 1.0 -> 0.5
      expect(value, 0.5);
      expect(logs, <String>['onChangeStart', 'onChanged']);

      // test slide
      await gesture.moveTo(startOfTheSliderTrack);
      await tester.pump();
      // changes from 0.5 -> 0.0
      expect(value, 0.0);
      await gesture.moveTo(endOfTheSliderTrack);
      await tester.pump();
      // changes from 0.0 -> 1.0
      expect(value, 1.0);
      expect(logs, <String>['onChangeStart', 'onChanged', 'onChanged', 'onChanged']);

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

      expect(logs, <String>['onChangeStart', 'onChanged', 'onChanged', 'onChanged', 'onChangeEnd']);
    });

    testWidgets('SliderInteraction.slideOnly', (WidgetTester tester) async {
      double value = 1.0;
      final Key sliderKey = UniqueKey();
      // (slider's left padding (overlayRadius), windowHeight / 2)
      const Offset startOfTheSliderTrack = Offset(24, 300);
      const Offset centerOfTheSlideTrack = Offset(400, 300);
      const Offset endOfTheSliderTrack = Offset(800 - 24, 300);
      final List<String> logs = <String>[];

      Widget buildApp() {
        return MaterialApp(
          home: Material(
            child: Center(
              child: StatefulBuilder(builder: (BuildContext _, StateSetter setState) {
                return Slider(
                  value: value,
                  key: sliderKey,
                  allowedInteraction: SliderInteraction.slideOnly,
                  onChangeStart: (double newValue) {
                    logs.add('onChangeStart');
                  },
                  onChanged: (double newValue) {
                    logs.add('onChanged');
                    setState(() {
                      value = newValue;
                    });
                  },
                  onChangeEnd: (double newValue) {
                    logs.add('onChangeEnd');
                  },
                );
              }),
            ),
          ),
        );
      }

      await tester.pumpWidget(buildApp());

      expect(logs, isEmpty);

      // test tap
      final TestGesture gesture = await tester.startGesture(centerOfTheSlideTrack);
      await tester.pump();
      // has no effect as tap is disabled, remains 1.0
      expect(value, 1.0);
      expect(logs, <String>['onChangeStart']);

      // test slide
      await gesture.moveTo(startOfTheSliderTrack);
      await tester.pump();
      // changes from 1.0 -> 0.5
      expect(value, 0.5);
      await gesture.moveTo(endOfTheSliderTrack);
      await tester.pump();
      // changes from 0.0 -> 1.0
      expect(value, 1.0);
      expect(logs, <String>['onChangeStart', 'onChanged', 'onChanged']);

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

      expect(logs, <String>['onChangeStart', 'onChanged', 'onChanged', 'onChangeEnd']);
    });

    testWidgets('SliderInteraction.slideThumb', (WidgetTester tester) async {
      double value = 1.0;
      final Key sliderKey = UniqueKey();
      // (slider's left padding (overlayRadius), windowHeight / 2)
      const Offset startOfTheSliderTrack = Offset(24, 300);
      const Offset centerOfTheSliderTrack = Offset(400, 300);
      const Offset endOfTheSliderTrack = Offset(800 - 24, 300);
      final List<String> logs = <String>[];

      Widget buildApp() {
        return MaterialApp(
          home: Material(
            child: Center(
              child: StatefulBuilder(builder: (BuildContext _, StateSetter setState) {
                return Slider(
                  value: value,
                  key: sliderKey,
                  allowedInteraction: SliderInteraction.slideThumb,
                  onChangeStart: (double newValue) {
                    logs.add('onChangeStart');
                  },
                  onChanged: (double newValue) {
                    logs.add('onChanged');
                    setState(() {
                      value = newValue;
                    });
                  },
                  onChangeEnd: (double newValue) {
                    logs.add('onChangeEnd');
                  },
                );
              }),
            ),
          ),
        );
      }

      await tester.pumpWidget(buildApp());

      expect(logs, isEmpty);

      // test tap
      final TestGesture gesture = await tester.startGesture(centerOfTheSliderTrack);
      await tester.pump();
      // has no effect, remains 1.0
      expect(value, 1.0);
      expect(logs, isEmpty);

      // test slide
      await gesture.moveTo(startOfTheSliderTrack);
      await tester.pump();
      // has no effect, remains 1.0
      expect(value, 1.0);
      expect(logs, isEmpty);

      // test slide thumb
      await gesture.up();
      await gesture.down(endOfTheSliderTrack); // where the thumb is
      await tester.pump();
      // has no effect, remains 1.0
      expect(value, 1.0);
      expect(logs, <String>['onChangeStart']);

      await gesture.moveTo(centerOfTheSliderTrack);
      await tester.pump();
      // changes from 1.0 -> 0.5
      expect(value, 0.5);
      expect(logs, <String>['onChangeStart', 'onChanged']);

      // test tap inside overlay but not on thumb, then slide
      await gesture.up();
      // default overlay radius is 12, so 10 is inside the overlay
      await gesture.down(centerOfTheSliderTrack.translate(-10, 0));
      await tester.pump();
      // changes from 1.0 -> 0.5
      expect(value, 0.5);
      expect(logs, <String>['onChangeStart', 'onChanged', 'onChangeEnd', 'onChangeStart']);

      await gesture.moveTo(endOfTheSliderTrack.translate(-10, 0));
      await tester.pump();
      // changes from 0.5 -> 1.0
      expect(value, 1.0);
      expect(logs, <String>['onChangeStart', 'onChanged', 'onChangeEnd', 'onChangeStart', 'onChanged']);

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

      expect(
        logs,
        <String>['onChangeStart', 'onChanged', 'onChangeEnd', 'onChangeStart', 'onChanged', 'onChangeEnd'],
      );
    });
  });
}
