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

import 'dart:math' as math;

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('Horizontal', () {
    testWidgets('gets local coordinates', (WidgetTester tester) async {
      int dragCancelCount = 0;
      final List<DragDownDetails> downDetails = <DragDownDetails>[];
      final List<DragEndDetails> endDetails = <DragEndDetails>[];
      final List<DragStartDetails> startDetails = <DragStartDetails>[];
      final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];

      final Key redContainer = UniqueKey();
      await tester.pumpWidget(
        Center(
          child: GestureDetector(
            onHorizontalDragCancel: () {
              dragCancelCount++;
            },
            onHorizontalDragDown: (DragDownDetails details) {
              downDetails.add(details);
            },
            onHorizontalDragEnd: (DragEndDetails details) {
              endDetails.add(details);
            },
            onHorizontalDragStart: (DragStartDetails details) {
              startDetails.add(details);
            },
            onHorizontalDragUpdate: (DragUpdateDetails details) {
              updateDetails.add(details);
            },
            child: Container(
              key: redContainer,
              width: 100,
              height: 150,
              color: Colors.red,
            ),
          ),
        ),
      );

      await tester.drag(find.byKey(redContainer), const Offset(100, 0));
      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, hasLength(1));
      expect(startDetails.single.localPosition, const Offset(50, 75));
      expect(startDetails.single.globalPosition, const Offset(400, 300));
      expect(updateDetails.last.localPosition, const Offset(50 + 100.0, 75));
      expect(updateDetails.last.globalPosition, const Offset(400 + 100.0, 300));
      expect(
        updateDetails.fold(Offset.zero, (Offset offset, DragUpdateDetails details) => offset + details.delta),
        const Offset(100, 0),
      );
      expect(
        updateDetails.fold(0.0, (double offset, DragUpdateDetails details) => offset + (details.primaryDelta ?? 0)),
        100.0,
      );
    });

    testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async {
      int dragCancelCount = 0;
      final List<DragDownDetails> downDetails = <DragDownDetails>[];
      final List<DragEndDetails> endDetails = <DragEndDetails>[];
      final List<DragStartDetails> startDetails = <DragStartDetails>[];
      final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];

      final Key redContainer = UniqueKey();
      await tester.pumpWidget(
        Center(
          child: Transform.scale(
            scale: 2.0,
            child: GestureDetector(
              onHorizontalDragCancel: () {
                dragCancelCount++;
              },
              onHorizontalDragDown: (DragDownDetails details) {
                downDetails.add(details);
              },
              onHorizontalDragEnd: (DragEndDetails details) {
                endDetails.add(details);
              },
              onHorizontalDragStart: (DragStartDetails details) {
                startDetails.add(details);
              },
              onHorizontalDragUpdate: (DragUpdateDetails details) {
                updateDetails.add(details);
              },
              onTap: () {
                // Competing gesture detector.
              },
              child: Container(
                key: redContainer,
                width: 100,
                height: 150,
                color: Colors.red,
              ),
            ),
          ),
        ),
      );

      // Move just above kTouchSlop should recognize drag.
      await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop + 1, 0));

      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, hasLength(1));
      expect(startDetails.single.localPosition, const Offset(50 + (kTouchSlop + 1) / 2, 75));
      expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300));
      expect(updateDetails, isEmpty);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();

      // Move just below kTouchSlop does not recognize drag.
      await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop - 1, 0));
      expect(dragCancelCount, 1);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, isEmpty);
      expect(startDetails, isEmpty);
      expect(updateDetails, isEmpty);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();

      // Move in two separate movements
      final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
      await gesture.moveBy(const Offset(kTouchSlop + 1, 30));
      await gesture.moveBy(const Offset(100, 10));
      await gesture.up();

      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, hasLength(1));
      expect(startDetails.single.localPosition, const Offset(50 + (kTouchSlop + 1) / 2, 75.0 + 30.0 / 2));
      expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300 + 30.0));
      expect(updateDetails.single.localPosition, startDetails.single.localPosition + const Offset(100.0 / 2, 10 / 2));
      expect(updateDetails.single.globalPosition, startDetails.single.globalPosition + const Offset(100.0, 10.0));
      expect(updateDetails.single.delta, const Offset(100.0 / 2, 0.0));
      expect(updateDetails.single.primaryDelta, 100.0 / 2);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();
    });

    testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async {
      int dragCancelCount = 0;
      final List<DragDownDetails> downDetails = <DragDownDetails>[];
      final List<DragEndDetails> endDetails = <DragEndDetails>[];
      final List<DragStartDetails> startDetails = <DragStartDetails>[];
      final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];

      final Key redContainer = UniqueKey();
      await tester.pumpWidget(
        Center(
          child: Transform.scale(
            scale: 0.5,
            child: GestureDetector(
              onHorizontalDragCancel: () {
                dragCancelCount++;
              },
              onHorizontalDragDown: (DragDownDetails details) {
                downDetails.add(details);
              },
              onHorizontalDragEnd: (DragEndDetails details) {
                endDetails.add(details);
              },
              onHorizontalDragStart: (DragStartDetails details) {
                startDetails.add(details);
              },
              onHorizontalDragUpdate: (DragUpdateDetails details) {
                updateDetails.add(details);
              },
              onTap: () {
                // Competing gesture detector.
              },
              child: Container(
                key: redContainer,
                width: 100,
                height: 150,
                color: Colors.red,
              ),
            ),
          ),
        ),
      );

      // Move just above kTouchSlop should recognize drag.
      await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop + 1, 0));

      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, hasLength(1));
      expect(startDetails.single.localPosition, const Offset(50 + (kTouchSlop + 1) * 2, 75));
      expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300));
      expect(updateDetails, isEmpty);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();

      // Move just below kTouchSlop does not recognize drag.
      await tester.drag(find.byKey(redContainer), const Offset(kTouchSlop - 1, 0));
      expect(dragCancelCount, 1);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, isEmpty);
      expect(startDetails, isEmpty);
      expect(updateDetails, isEmpty);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();

      // Move in two separate movements
      final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
      await gesture.moveBy(const Offset(kTouchSlop + 1, 30));
      await gesture.moveBy(const Offset(100, 10));
      await gesture.up();

      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, hasLength(1));
      expect(startDetails.single.localPosition, const Offset(50 + (kTouchSlop + 1) * 2, 75.0 + 30.0 * 2));
      expect(startDetails.single.globalPosition, const Offset(400 + (kTouchSlop + 1), 300 + 30.0));
      expect(updateDetails.single.localPosition, startDetails.single.localPosition + const Offset(100.0 * 2, 10.0 * 2.0));
      expect(updateDetails.single.globalPosition, startDetails.single.globalPosition + const Offset(100.0, 10.0));
      expect(updateDetails.single.delta, const Offset(100.0 * 2.0, 0.0));
      expect(updateDetails.single.primaryDelta, 100.0 * 2);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();
    });

    testWidgets('kTouchSlop is evaluated in the global coordinate space when rotated 45 degrees', (WidgetTester tester) async {
      int dragCancelCount = 0;
      final List<DragDownDetails> downDetails = <DragDownDetails>[];
      final List<DragEndDetails> endDetails = <DragEndDetails>[];
      final List<DragStartDetails> startDetails = <DragStartDetails>[];
      final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];

      final Key redContainer = UniqueKey();
      await tester.pumpWidget(
        Center(
          child: Transform.rotate(
            angle: math.pi / 4,
            child: GestureDetector(
              onHorizontalDragCancel: () {
                dragCancelCount++;
              },
              onHorizontalDragDown: (DragDownDetails details) {
                downDetails.add(details);
              },
              onHorizontalDragEnd: (DragEndDetails details) {
                endDetails.add(details);
              },
              onHorizontalDragStart: (DragStartDetails details) {
                startDetails.add(details);
              },
              onHorizontalDragUpdate: (DragUpdateDetails details) {
                updateDetails.add(details);
              },
              onTap: () {
                // Competing gesture detector.
              },
              child: Container(
                key: redContainer,
                width: 100,
                height: 150,
                color: Colors.red,
              ),
            ),
          ),
        ),
      );

      // Move just below kTouchSlop should not recognize drag.
      const Offset moveBy1 = Offset(kTouchSlop/ 2, kTouchSlop / 2);
      expect(moveBy1.distance, lessThan(kTouchSlop));
      await tester.drag(find.byKey(redContainer), moveBy1);
      expect(dragCancelCount, 1);
      expect(downDetails.single.localPosition, within(distance: 0.0001, from: const Offset(50, 75)));
      expect(downDetails.single.globalPosition, within(distance: 0.0001, from: const Offset(400, 300)));
      expect(endDetails, isEmpty);
      expect(startDetails, isEmpty);
      expect(updateDetails, isEmpty);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();

      // Move above kTouchSlop recognizes drag.
      final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
      await gesture.moveBy(const Offset(kTouchSlop, kTouchSlop));
      await gesture.moveBy(const Offset(3, 4));
      await gesture.up();

      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition,  within(distance: 0.0001, from: const Offset(50, 75)));
      expect(downDetails.single.globalPosition,  within(distance: 0.0001, from: const Offset(400, 300)));
      expect(endDetails, hasLength(1));
      expect(startDetails, hasLength(1));
      expect(updateDetails.single.globalPosition, within(distance: 0.0001, from: const Offset(400 + kTouchSlop + 3, 300 + kTouchSlop + 4)));
      expect(updateDetails.single.delta, within(distance: 0.1, from: const Offset(5, 0.0))); // sqrt(3^2 + 4^2)
      expect(updateDetails.single.primaryDelta, within<double>(distance: 0.1, from: 5.0)); // sqrt(3^2 + 4^2)
    });
  });

  group('Vertical', () {
    testWidgets('gets local coordinates', (WidgetTester tester) async {
      int dragCancelCount = 0;
      final List<DragDownDetails> downDetails = <DragDownDetails>[];
      final List<DragEndDetails> endDetails = <DragEndDetails>[];
      final List<DragStartDetails> startDetails = <DragStartDetails>[];
      final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];

      final Key redContainer = UniqueKey();
      await tester.pumpWidget(
        Center(
          child: GestureDetector(
            onVerticalDragCancel: () {
              dragCancelCount++;
            },
            onVerticalDragDown: (DragDownDetails details) {
              downDetails.add(details);
            },
            onVerticalDragEnd: (DragEndDetails details) {
              endDetails.add(details);
            },
            onVerticalDragStart: (DragStartDetails details) {
              startDetails.add(details);
            },
            onVerticalDragUpdate: (DragUpdateDetails details) {
              updateDetails.add(details);
            },
            child: Container(
              key: redContainer,
              width: 100,
              height: 150,
              color: Colors.red,
            ),
          ),
        ),
      );

      await tester.drag(find.byKey(redContainer), const Offset(0, 100));
      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, hasLength(1));
      expect(startDetails.single.localPosition, const Offset(50, 75));
      expect(startDetails.single.globalPosition, const Offset(400, 300));
      expect(updateDetails.last.localPosition, const Offset(50, 75 + 100.0));
      expect(updateDetails.last.globalPosition, const Offset(400, 300 + 100.0));
      expect(
        updateDetails.fold(Offset.zero, (Offset offset, DragUpdateDetails details) => offset + details.delta),
        const Offset(0, 100),
      );
      expect(
        updateDetails.fold(0.0, (double offset, DragUpdateDetails details) => offset + (details.primaryDelta ?? 0)),
        100.0,
      );
    });

    testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled up', (WidgetTester tester) async {
      int dragCancelCount = 0;
      final List<DragDownDetails> downDetails = <DragDownDetails>[];
      final List<DragEndDetails> endDetails = <DragEndDetails>[];
      final List<DragStartDetails> startDetails = <DragStartDetails>[];
      final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];

      final Key redContainer = UniqueKey();
      await tester.pumpWidget(
        Center(
          child: Transform.scale(
            scale: 2.0,
            child: GestureDetector(
              onVerticalDragCancel: () {
                dragCancelCount++;
              },
              onVerticalDragDown: (DragDownDetails details) {
                downDetails.add(details);
              },
              onVerticalDragEnd: (DragEndDetails details) {
                endDetails.add(details);
              },
              onVerticalDragStart: (DragStartDetails details) {
                startDetails.add(details);
              },
              onVerticalDragUpdate: (DragUpdateDetails details) {
                updateDetails.add(details);
              },
              onTap: () {
                // Competing gesture detector.
              },
              child: Container(
                key: redContainer,
                width: 100,
                height: 150,
                color: Colors.red,
              ),
            ),
          ),
        ),
      );

      // Move just above kTouchSlop should recognize drag.
      await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop + 1));

      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, hasLength(1));
      expect(startDetails.single.localPosition, const Offset(50, 75 + (kTouchSlop + 1) / 2));
      expect(startDetails.single.globalPosition, const Offset(400, 300 + (kTouchSlop + 1)));
      expect(updateDetails, isEmpty);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();

      // Move just below kTouchSlop does not recognize drag.
      await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop - 1));
      expect(dragCancelCount, 1);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, isEmpty);
      expect(startDetails, isEmpty);
      expect(updateDetails, isEmpty);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();

      // Move in two separate movements
      final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
      await gesture.moveBy(const Offset(30, kTouchSlop + 1));
      await gesture.moveBy(const Offset(10, 100));
      await gesture.up();

      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, hasLength(1));
      expect(startDetails.single.localPosition, const Offset(50 + 30.0 / 2, 75.0 + (kTouchSlop + 1) / 2));
      expect(startDetails.single.globalPosition, const Offset(400 + 30.0, 300 + (kTouchSlop + 1)));
      expect(updateDetails.single.localPosition, startDetails.single.localPosition + const Offset(10.0 / 2, 100.0 / 2));
      expect(updateDetails.single.globalPosition, startDetails.single.globalPosition + const Offset(10.0, 100.0));
      expect(updateDetails.single.delta, const Offset(0.0, 100.0 / 2));
      expect(updateDetails.single.primaryDelta, 100.0 / 2);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();
    });

    testWidgets('kTouchSlop is evaluated in the global coordinate space when scaled down', (WidgetTester tester) async {
      int dragCancelCount = 0;
      final List<DragDownDetails> downDetails = <DragDownDetails>[];
      final List<DragEndDetails> endDetails = <DragEndDetails>[];
      final List<DragStartDetails> startDetails = <DragStartDetails>[];
      final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];

      final Key redContainer = UniqueKey();
      await tester.pumpWidget(
        Center(
          child: Transform.scale(
            scale: 0.5,
            child: GestureDetector(
              onVerticalDragCancel: () {
                dragCancelCount++;
              },
              onVerticalDragDown: (DragDownDetails details) {
                downDetails.add(details);
              },
              onVerticalDragEnd: (DragEndDetails details) {
                endDetails.add(details);
              },
              onVerticalDragStart: (DragStartDetails details) {
                startDetails.add(details);
              },
              onVerticalDragUpdate: (DragUpdateDetails details) {
                updateDetails.add(details);
              },
              onTap: () {
                // Competing gesture detector.
              },
              child: Container(
                key: redContainer,
                width: 100,
                height: 150,
                color: Colors.red,
              ),
            ),
          ),
        ),
      );

      // Move just above kTouchSlop should recognize drag.
      await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop + 1));

      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, hasLength(1));
      expect(startDetails.single.localPosition, const Offset(50, 75 + (kTouchSlop + 1) * 2));
      expect(startDetails.single.globalPosition, const Offset(400, 300 + (kTouchSlop + 1)));
      expect(updateDetails, isEmpty);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();

      // Move just below kTouchSlop does not recognize drag.
      await tester.drag(find.byKey(redContainer), const Offset(0, kTouchSlop - 1));
      expect(dragCancelCount, 1);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, isEmpty);
      expect(startDetails, isEmpty);
      expect(updateDetails, isEmpty);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();

      // Move in two separate movements
      final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
      await gesture.moveBy(const Offset(30, kTouchSlop + 1));
      await gesture.moveBy(const Offset(10, 100));
      await gesture.up();

      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition, const Offset(50, 75));
      expect(downDetails.single.globalPosition, const Offset(400, 300));
      expect(endDetails, hasLength(1));
      expect(startDetails.single.localPosition, const Offset(50 + 30.0 * 2, 75.0 + (kTouchSlop + 1) * 2));
      expect(startDetails.single.globalPosition, const Offset(400 + 30.0, 300 + (kTouchSlop + 1)));
      expect(updateDetails.single.localPosition, startDetails.single.localPosition + const Offset(10.0 * 2, 100.0 * 2.0));
      expect(updateDetails.single.globalPosition, startDetails.single.globalPosition + const Offset(10.0, 100.0));
      expect(updateDetails.single.delta, const Offset(0.0, 100.0 * 2.0));
      expect(updateDetails.single.primaryDelta, 100.0 * 2);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();
    });

    testWidgets('kTouchSlop is evaluated in the global coordinate space when rotated 45 degrees', (WidgetTester tester) async {
      int dragCancelCount = 0;
      final List<DragDownDetails> downDetails = <DragDownDetails>[];
      final List<DragEndDetails> endDetails = <DragEndDetails>[];
      final List<DragStartDetails> startDetails = <DragStartDetails>[];
      final List<DragUpdateDetails> updateDetails = <DragUpdateDetails>[];

      final Key redContainer = UniqueKey();
      await tester.pumpWidget(
        Center(
          child: Transform.rotate(
            angle: math.pi / 4,
            child: GestureDetector(
              onVerticalDragCancel: () {
                dragCancelCount++;
              },
              onVerticalDragDown: (DragDownDetails details) {
                downDetails.add(details);
              },
              onVerticalDragEnd: (DragEndDetails details) {
                endDetails.add(details);
              },
              onVerticalDragStart: (DragStartDetails details) {
                startDetails.add(details);
              },
              onVerticalDragUpdate: (DragUpdateDetails details) {
                updateDetails.add(details);
              },
              onTap: () {
                // Competing gesture detector.
              },
              child: Container(
                key: redContainer,
                width: 100,
                height: 150,
                color: Colors.red,
              ),
            ),
          ),
        ),
      );

      // Move just below kTouchSlop should not recognize drag.
      const Offset moveBy1 = Offset(kTouchSlop/ 2, kTouchSlop / 2);
      expect(moveBy1.distance, lessThan(kTouchSlop));
      await tester.drag(find.byKey(redContainer), moveBy1);
      expect(dragCancelCount, 1);
      expect(downDetails.single.localPosition, within(distance: 0.0001, from: const Offset(50, 75)));
      expect(downDetails.single.globalPosition, within(distance: 0.0001, from: const Offset(400, 300)));
      expect(endDetails, isEmpty);
      expect(startDetails, isEmpty);
      expect(updateDetails, isEmpty);

      dragCancelCount = 0;
      downDetails.clear();
      endDetails.clear();
      startDetails.clear();
      updateDetails.clear();

      // Move above kTouchSlop recognizes drag.
      final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(redContainer)));
      await gesture.moveBy(const Offset(kTouchSlop, kTouchSlop));
      await gesture.moveBy(const Offset(-4, 3));
      await gesture.up();

      expect(dragCancelCount, 0);
      expect(downDetails.single.localPosition,  within(distance: 0.0001, from: const Offset(50, 75)));
      expect(downDetails.single.globalPosition,  within(distance: 0.0001, from: const Offset(400, 300)));
      expect(endDetails, hasLength(1));
      expect(startDetails, hasLength(1));
      expect(updateDetails.single.globalPosition, within(distance: 0.0001, from: const Offset(400 + kTouchSlop - 4, 300 + kTouchSlop + 3)));
      expect(updateDetails.single.delta, within(distance: 0.1, from: const Offset(0.0, 5.0))); // sqrt(3^2 + 4^2)
      expect(updateDetails.single.primaryDelta, within<double>(distance: 0.1, from: 5.0)); // sqrt(3^2 + 4^2)
    });
  });
}