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

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

void main() {
  testWidgets('Drag and drop - control test', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
    int dragStartedCount = 0;

    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new Draggable<int>(
            data: 1,
            child: new Text('Source'),
            feedback: new Text('Dragging'),
            onDragStarted: () {
              ++dragStartedCount;
            },
          ),
          new DragTarget<int>(
            builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
              return new Container(height: 100.0, child: new Text('Target'));
            },
            onAccept: (int data) {
              accepted.add(data);
            }
          ),
        ]
      )
    ));

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(dragStartedCount, 0);

    final Point firstLocation = tester.getCenter(find.text('Source'));
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(dragStartedCount, 1);

    final Point secondLocation = tester.getCenter(find.text('Target'));
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(dragStartedCount, 1);

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

    expect(accepted, equals(<int>[1]));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(dragStartedCount, 1);
  });

  testWidgets('Drag and drop - dragging over button', (WidgetTester tester) async {
    final List<String> events = <String>[];
    Point firstLocation, secondLocation;

    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new Draggable<int>(
            data: 1,
            child: new Text('Source'),
            feedback: new Text('Dragging')
          ),
          new Stack(
            children: <Widget>[
              new GestureDetector(
                behavior: HitTestBehavior.opaque,
                onTap: () {
                  events.add('tap');
                },
                child: new Container(child: new Text('Button')
              )
            ),
            new DragTarget<int>(
              builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
                return new IgnorePointer(
                  child: new Container(child: new Text('Target'))
                );
              },
              onAccept: (int data) {
                events.add('drop');
              }),
            ]
          ),
        ]
      )
    ));

    expect(events, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('Button'), findsOneWidget);

    // taps (we check both to make sure the test is consistent)

    expect(events, isEmpty);
    await tester.tap(find.text('Button'));
    expect(events, equals(<String>['tap']));
    events.clear();

    expect(events, isEmpty);
    await tester.tap(find.text('Target'));
    expect(events, equals(<String>['tap']));
    events.clear();

    // drag and drop

    firstLocation = tester.getCenter(find.text('Source'));
    TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    secondLocation = tester.getCenter(find.text('Target'));
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(events, isEmpty);
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>['drop']));
    events.clear();

    // drag and tap and drop

    firstLocation = tester.getCenter(find.text('Source'));
    gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    secondLocation = tester.getCenter(find.text('Target'));
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(events, isEmpty);
    await tester.tap(find.text('Button'));
    await tester.tap(find.text('Target'));
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>['tap', 'tap', 'drop']));
    events.clear();
  });

  testWidgets('Drag and drop - tapping button', (WidgetTester tester) async {
    final List<String> events = <String>[];
    Point firstLocation, secondLocation;

    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new Draggable<int>(
            data: 1,
            child: new GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: () {
                events.add('tap');
              },
              child: new Container(child: new Text('Button'))
            ),
            feedback: new Text('Dragging')
          ),
          new DragTarget<int>(
            builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
              return new Text('Target');
            },
            onAccept: (int data) {
              events.add('drop');
            }
          ),
        ]
      )
    ));

    expect(events, isEmpty);
    expect(find.text('Button'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

    expect(events, isEmpty);
    await tester.tap(find.text('Button'));
    expect(events, equals(<String>['tap']));
    events.clear();

    firstLocation = tester.getCenter(find.text('Button'));
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    secondLocation = tester.getCenter(find.text('Target'));
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(events, isEmpty);
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>['drop']));
    events.clear();
  });

  testWidgets('Drag and drop - long press draggable, short press', (WidgetTester tester) async {
    final List<String> events = <String>[];
    Point firstLocation, secondLocation;

    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new LongPressDraggable<int>(
            data: 1,
            child: new Text('Source'),
            feedback: new Text('Dragging')
          ),
          new DragTarget<int>(
            builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
              return new Text('Target');
            },
            onAccept: (int data) {
              events.add('drop');
            }
          ),
        ]
      )
    ));

    expect(events, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

    expect(events, isEmpty);
    await tester.tap(find.text('Source'));
    expect(events, isEmpty);

    firstLocation = tester.getCenter(find.text('Source'));
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    secondLocation = tester.getCenter(find.text('Target'));
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(events, isEmpty);
    await gesture.up();
    await tester.pump();
    expect(events, isEmpty);
  });

  testWidgets('Drag and drop - long press draggable, long press', (WidgetTester tester) async {
    final List<String> events = <String>[];
    Point firstLocation, secondLocation;

    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new Draggable<int>(
            data: 1,
            child: new Text('Source'),
            feedback: new Text('Dragging')
          ),
          new DragTarget<int>(
            builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
              return new Text('Target');
            },
            onAccept: (int data) {
              events.add('drop');
            }
          ),
        ]
      )
    ));

    expect(events, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

    expect(events, isEmpty);
    await tester.tap(find.text('Source'));
    expect(events, isEmpty);

    firstLocation = tester.getCenter(find.text('Source'));
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    await tester.pump(const Duration(seconds: 20));

    secondLocation = tester.getCenter(find.text('Target'));
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(events, isEmpty);
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>['drop']));
  });

  testWidgets('Drag and drop - horizontal and vertical draggables in vertical block', (WidgetTester tester) async {
    final List<String> events = <String>[];
    Point firstLocation, secondLocation, thirdLocation;

    await tester.pumpWidget(new MaterialApp(
      home: new ListView(
        children: <Widget>[
          new DragTarget<int>(
            builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
              return new Text('Target');
            },
            onAccept: (int data) {
              events.add('drop $data');
            }
          ),
          new Container(height: 400.0),
          new Draggable<int>(
            data: 1,
            child: new Text('H'),
            feedback: new Text('Dragging'),
            affinity: Axis.horizontal,
          ),
          new Draggable<int>(
            data: 2,
            child: new Text('V'),
            feedback: new Text('Dragging'),
            affinity: Axis.vertical,
          ),
          new Container(height: 500.0),
          new Container(height: 500.0),
          new Container(height: 500.0),
          new Container(height: 500.0),
        ]
      )
    ));

    expect(events, isEmpty);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('H'), findsOneWidget);
    expect(find.text('V'), findsOneWidget);

    // vertical draggable drags vertically
    expect(events, isEmpty);
    firstLocation = tester.getCenter(find.text('V'));
    secondLocation = tester.getCenter(find.text('Target'));
    TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>['drop 2']));
    expect(tester.getCenter(find.text('Target')).y, greaterThan(0.0));
    events.clear();

    // horizontal draggable drags horizontally
    expect(events, isEmpty);
    firstLocation = tester.getTopLeft(find.text('H'));
    secondLocation = tester.getTopRight(find.text('H'));
    thirdLocation = tester.getCenter(find.text('Target'));
    gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
    await gesture.moveTo(thirdLocation);
    await tester.pump();
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>['drop 1']));
    expect(tester.getCenter(find.text('Target')).y, greaterThan(0.0));
    events.clear();

    // vertical draggable drags horizontally when there's no competition
    // from other gesture detectors
    expect(events, isEmpty);
    firstLocation = tester.getTopLeft(find.text('V'));
    secondLocation = tester.getTopRight(find.text('V'));
    thirdLocation = tester.getCenter(find.text('Target'));
    gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
    await gesture.moveTo(thirdLocation);
    await tester.pump();
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>['drop 2']));
    expect(tester.getCenter(find.text('Target')).y, greaterThan(0.0));
    events.clear();

    // horizontal draggable doesn't drag vertically when there is competition
    // for vertical gestures
    expect(events, isEmpty);
    firstLocation = tester.getCenter(find.text('H'));
    secondLocation = tester.getCenter(find.text('Target'));
    gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump(); // scrolls off screen!
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>[]));
    expect(find.text('Target'), findsNothing);
    events.clear();
  });

  testWidgets('Drag and drop - horizontal and vertical draggables in horizontal block', (WidgetTester tester) async {
    final List<String> events = <String>[];
    Point firstLocation, secondLocation, thirdLocation;

    await tester.pumpWidget(new MaterialApp(
      home: new ListView(
        scrollDirection: Axis.horizontal,
        children: <Widget>[
          new DragTarget<int>(
            builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
              return new Text('Target');
            },
            onAccept: (int data) {
              events.add('drop $data');
            }
          ),
          new Container(width: 400.0),
          new Draggable<int>(
            data: 1,
            child: new Text('H'),
            feedback: new Text('Dragging'),
            affinity: Axis.horizontal,
          ),
          new Draggable<int>(
            data: 2,
            child: new Text('V'),
            feedback: new Text('Dragging'),
            affinity: Axis.vertical,
          ),
          new Container(width: 500.0),
          new Container(width: 500.0),
          new Container(width: 500.0),
          new Container(width: 500.0),
        ]
      )
    ));

    expect(events, isEmpty);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('H'), findsOneWidget);
    expect(find.text('V'), findsOneWidget);

    // horizontal draggable drags horizontally
    expect(events, isEmpty);
    firstLocation = tester.getCenter(find.text('H'));
    secondLocation = tester.getCenter(find.text('Target'));
    TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>['drop 1']));
    expect(tester.getCenter(find.text('Target')).x, greaterThan(0.0));
    events.clear();

    // vertical draggable drags vertically
    expect(events, isEmpty);
    firstLocation = tester.getTopLeft(find.text('V'));
    secondLocation = tester.getBottomLeft(find.text('V'));
    thirdLocation = tester.getCenter(find.text('Target'));
    gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
    await gesture.moveTo(thirdLocation);
    await tester.pump();
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>['drop 2']));
    expect(tester.getCenter(find.text('Target')).x, greaterThan(0.0));
    events.clear();

    // horizontal draggable drags vertically when there's no competition
    // from other gesture detectors
    expect(events, isEmpty);
    firstLocation = tester.getTopLeft(find.text('H'));
    secondLocation = tester.getBottomLeft(find.text('H'));
    thirdLocation = tester.getCenter(find.text('Target'));
    gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
    await gesture.moveTo(thirdLocation);
    await tester.pump();
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>['drop 1']));
    expect(tester.getCenter(find.text('Target')).x, greaterThan(0.0));
    events.clear();

    // vertical draggable doesn't drag horizontally when there is competition
    // for horizontal gestures
    expect(events, isEmpty);
    firstLocation = tester.getCenter(find.text('V'));
    secondLocation = tester.getCenter(find.text('Target'));
    gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump(); // scrolls off screen!
    await gesture.up();
    await tester.pump();
    expect(events, equals(<String>[]));
    expect(find.text('Target'), findsNothing);
    events.clear();
  });

  testWidgets('Drag and drop - onDraggableDropped not called if dropped on accepting target', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
    bool onDraggableCanceledCalled = false;

    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new Draggable<int>(
            data: 1,
            child: new Text('Source'),
            feedback: new Text('Dragging'),
            onDraggableCanceled: (Velocity velocity, Offset offset) {
              onDraggableCanceledCalled = true;
            }
          ),
          new DragTarget<int>(
            builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
              return new Container(height: 100.0, child: new Text('Target'));
            },
            onAccept: (int data) {
              accepted.add(data);
            }
          ),
        ]
      )
    ));

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

    final Point firstLocation = tester.getCenter(find.text('Source'));
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

    final Point secondLocation = tester.getCenter(find.text('Target'));
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

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

    expect(accepted, equals(<int>[1]));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);
  });

  testWidgets('Drag and drop - onDraggableDropped called if dropped on non-accepting target', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
    bool onDraggableCanceledCalled = false;
    Velocity onDraggableCanceledVelocity;
    Offset onDraggableCanceledOffset;

    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new Draggable<int>(
            data: 1,
            child: new Text('Source'),
            feedback: new Text('Dragging'),
            onDraggableCanceled: (Velocity velocity, Offset offset) {
              onDraggableCanceledCalled = true;
              onDraggableCanceledVelocity = velocity;
              onDraggableCanceledOffset = offset;
            }
          ),
          new DragTarget<int>(
            builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
              return new Container(
                height: 100.0,
                child: new Text('Target')
              );
            },
            onWillAccept: (int data) => false
          ),
        ]
      )
    ));

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

    final Point firstLocation = tester.getTopLeft(find.text('Source'));
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

    final Point secondLocation = tester.getCenter(find.text('Target'));
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

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

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isTrue);
    expect(onDraggableCanceledVelocity, equals(Velocity.zero));
    expect(onDraggableCanceledOffset, equals(new Offset(secondLocation.x, secondLocation.y)));
  });

  testWidgets('Drag and drop - onDraggableDropped called if dropped on non-accepting target with correct velocity', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
    bool onDraggableCanceledCalled = false;
    Velocity onDraggableCanceledVelocity;
    Offset onDraggableCanceledOffset;

    await tester.pumpWidget(new MaterialApp(
      home: new Column(children: <Widget>[
        new Draggable<int>(
          data: 1,
          child: new Text('Source'),
          feedback: new Text('Source'),
          onDraggableCanceled: (Velocity velocity, Offset offset) {
            onDraggableCanceledCalled = true;
            onDraggableCanceledVelocity = velocity;
            onDraggableCanceledOffset = offset;
          }
        ),
        new DragTarget<int>(
          builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
            return new Container(
              height: 100.0,
              child: new Text('Target')
            );
          },
          onWillAccept: (int data) => false),
        ]
      )
    ));

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

    final Point flingStart = tester.getTopLeft(find.text('Source'));
    await tester.flingFrom(flingStart, const Offset(0.0, 100.0), 1000.0);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isTrue);
    expect(onDraggableCanceledVelocity.pixelsPerSecond.dx.abs(), lessThan(0.0000001));
    expect((onDraggableCanceledVelocity.pixelsPerSecond.dy - 1000.0).abs(), lessThan(0.0000001));
    expect(onDraggableCanceledOffset, equals(new Offset(flingStart.x, flingStart.y) + const Offset(0.0, 100.0)));
  });

  testWidgets('Drag and drop - allow pass thru of unaccepted data test', (WidgetTester tester) async {
    final List<int> acceptedInts = <int>[];
    final List<double> acceptedDoubles = <double>[];

    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new Draggable<int>(
            data: 1,
            child: new Text('IntSource'),
            feedback: new Text('IntDragging')
          ),
          new Draggable<double>(
            data: 1.0,
            child: new Text('DoubleSource'),
            feedback: new Text('DoubleDragging')
          ),
          new Stack(
            children: <Widget>[
              new DragTarget<int>(
                builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
                  return new IgnorePointer(
                    child: new Container(
                      height: 100.0,
                      child: new Text('Target1')
                    )
                  );
                },
                onAccept: (int data) {
                  acceptedInts.add(data);
                }
              ),
              new DragTarget<double>(
                builder: (BuildContext context, List<double> data, List<dynamic> rejects) {
                  return new IgnorePointer(
                    child: new Container(
                      height: 100.0,
                      child: new Text('Target2')
                    )
                  );
                },
                onAccept: (double data) {
                  acceptedDoubles.add(data);
                }
              ),
            ]
          )
        ]
      )
    ));

    expect(acceptedInts, isEmpty);
    expect(acceptedDoubles, isEmpty);
    expect(find.text('IntSource'), findsOneWidget);
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleSource'), findsOneWidget);
    expect(find.text('DoubleDragging'), findsNothing);
    expect(find.text('Target1'), findsOneWidget);
    expect(find.text('Target2'), findsOneWidget);

    final Point intLocation = tester.getCenter(find.text('IntSource'));
    final Point doubleLocation = tester.getCenter(find.text('DoubleSource'));
    final Point targetLocation = tester.getCenter(find.text('Target1'));

    // Drag the double draggable.
    final TestGesture doubleGesture = await tester.startGesture(doubleLocation, pointer: 7);
    await tester.pump();

    expect(acceptedInts, isEmpty);
    expect(acceptedDoubles, isEmpty);
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsOneWidget);

    await doubleGesture.moveTo(targetLocation);
    await tester.pump();

    expect(acceptedInts, isEmpty);
    expect(acceptedDoubles, isEmpty);
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsOneWidget);

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

    expect(acceptedInts, isEmpty);
    expect(acceptedDoubles, equals(<double>[1.0]));
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsNothing);

    acceptedDoubles.clear();

    // Drag the int draggable.
    final TestGesture intGesture = await tester.startGesture(intLocation, pointer: 7);
    await tester.pump();

    expect(acceptedInts, isEmpty);
    expect(acceptedDoubles, isEmpty);
    expect(find.text('IntDragging'), findsOneWidget);
    expect(find.text('DoubleDragging'), findsNothing);

    await intGesture.moveTo(targetLocation);
    await tester.pump();

    expect(acceptedInts, isEmpty);
    expect(acceptedDoubles, isEmpty);
    expect(find.text('IntDragging'), findsOneWidget);
    expect(find.text('DoubleDragging'), findsNothing);

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

    expect(acceptedInts, equals(<int>[1]));
    expect(acceptedDoubles, isEmpty);
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsNothing);
  });

  testWidgets('Drag and drop - allow pass thru of unaccepted data twice test', (WidgetTester tester) async {
    final List<DragTargetData> acceptedDragTargetDatas = <DragTargetData>[];
    final List<ExtendedDragTargetData> acceptedExtendedDragTargetDatas = <ExtendedDragTargetData>[];
    final DragTargetData dragTargetData = new DragTargetData();
    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new Draggable<DragTargetData>(
            data: dragTargetData,
            child: new Text('Source'),
            feedback: new Text('Dragging')
          ),
          new Stack(
            children: <Widget>[
              new DragTarget<DragTargetData>(
                builder: (BuildContext context, List<DragTargetData> data, List<dynamic> rejects) {
                  return new IgnorePointer(
                    child: new Container(
                      height: 100.0,
                      child: new Text('Target1')
                    )
                  );
                }, onAccept: (DragTargetData data) {
                  acceptedDragTargetDatas.add(data);
                }
              ),
              new DragTarget<ExtendedDragTargetData>(
                builder: (BuildContext context, List<ExtendedDragTargetData> data, List<dynamic> rejects) {
                  return new IgnorePointer(
                    child: new Container(
                      height: 100.0,
                      child: new Text('Target2')
                    )
                  );
                },
                onAccept: (ExtendedDragTargetData data) {
                  acceptedExtendedDragTargetDatas.add(data);
                }
              ),
            ]
          )
        ]
      )
    ));

    final Point dragTargetLocation = tester.getCenter(find.text('Source'));
    final Point targetLocation = tester.getCenter(find.text('Target1'));

    for (int i = 0; i < 2; i += 1) {
      final TestGesture gesture = await tester.startGesture(dragTargetLocation);
      await tester.pump();
      await gesture.moveTo(targetLocation);
      await tester.pump();
      await gesture.up();
      await tester.pump();

      expect(acceptedDragTargetDatas, equals(<DragTargetData>[dragTargetData]));
      expect(acceptedExtendedDragTargetDatas, isEmpty);

      acceptedDragTargetDatas.clear();
      await tester.pump();
    }
  });

  testWidgets('Drag and drop - maxSimultaneousDrags', (WidgetTester tester) async {
    final List<int> accepted = <int>[];

    Widget build(int maxSimultaneousDrags) {
      return new MaterialApp(
        home: new Column(
          children: <Widget>[
            new Draggable<int>(
              data: 1,
              maxSimultaneousDrags: maxSimultaneousDrags,
              child: new Text('Source'),
              feedback: new Text('Dragging')
            ),
            new DragTarget<int>(
              builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
                return new Container(height: 100.0, child: new Text('Target'));
              },
              onAccept: (int data) {
                accepted.add(data);
              }
            ),
          ]
        )
      );
    }

    await tester.pumpWidget(build(0));

    final Point firstLocation = tester.getCenter(find.text('Source'));
    final Point secondLocation = tester.getCenter(find.text('Target'));

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

    await gesture.up();

    await tester.pumpWidget(build(2));

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

    final TestGesture gesture1 = await tester.startGesture(firstLocation, pointer: 8);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

    final TestGesture gesture2 = await tester.startGesture(firstLocation, pointer: 9);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNWidgets(2));
    expect(find.text('Target'), findsOneWidget);

    final TestGesture gesture3 = await tester.startGesture(firstLocation, pointer: 10);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNWidgets(2));
    expect(find.text('Target'), findsOneWidget);

    await gesture1.moveTo(secondLocation);
    await gesture2.moveTo(secondLocation);
    await gesture3.moveTo(secondLocation);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNWidgets(2));
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, equals(<int>[1]));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, equals(<int>[1, 1]));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, equals(<int>[1, 1]));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
  });

  testWidgets('Draggable disposes recognizer', (WidgetTester tester) async {
    bool didTap = false;
    await tester.pumpWidget(new Overlay(
      initialEntries: <OverlayEntry>[
        new OverlayEntry(
          builder: (BuildContext context) => new GestureDetector(
            onTap: () {
              didTap = true;
            },
            child: new Draggable<dynamic>(
              child: new Container(
                decoration: const BoxDecoration(
                  backgroundColor: const Color(0xFFFFFF00)
                )
              ),
              feedback: new Container(
                width: 100.0,
                height: 100.0,
                decoration: const BoxDecoration(
                  backgroundColor: const Color(0xFFFF0000)
                )
              )
            )
          )
        )
      ]
    ));

    await tester.startGesture(const Point(10.0, 10.0));
    expect(didTap, isFalse);

    // This tears down the draggable without terminating the gesture sequence,
    // which used to trigger asserts in the multi-drag gesture recognizer.
    await tester.pumpWidget(new Container(key: new UniqueKey()));
    expect(didTap, isFalse);
  });

  // Regression test for https://github.com/flutter/flutter/issues/6128.
  testWidgets('Draggable plays nice with onTap', (WidgetTester tester) async {
    await tester.pumpWidget(new Overlay(
      initialEntries: <OverlayEntry>[
        new OverlayEntry(
          builder: (BuildContext context) => new GestureDetector(
            onTap: () { /* registers a tap recognizer */ },
            child: new Draggable<dynamic>(
              child: new Container(
                decoration: const BoxDecoration(
                  backgroundColor: const Color(0xFFFFFF00)
                )
              ),
              feedback: new Container(
                width: 100.0,
                height: 100.0,
                decoration: const BoxDecoration(
                  backgroundColor: const Color(0xFFFF0000)
                )
              )
            )
          )
        )
      ]
    ));

    final TestGesture firstGesture = await tester.startGesture(const Point(10.0, 10.0), pointer: 24);
    final TestGesture secondGesture = await tester.startGesture(const Point(10.0, 20.0), pointer: 25);

    await firstGesture.moveBy(const Offset(100.0, 0.0));
    await secondGesture.up();
  });

  testWidgets('DragTarget does not set state when remove from the tree', (WidgetTester tester) async {
    final List<String> events = <String>[];
    Point firstLocation, secondLocation;

    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new Draggable<int>(
            data: 1,
            child: new Text('Source'),
            feedback: new Text('Dragging')
          ),
          new DragTarget<int>(
            builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
              return new Text('Target');
            },
            onAccept: (int data) {
              events.add('drop');
            }
          ),
        ]
      )
    ));

    expect(events, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

    expect(events, isEmpty);
    await tester.tap(find.text('Source'));
    expect(events, isEmpty);

    firstLocation = tester.getCenter(find.text('Source'));
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    await tester.pump(const Duration(seconds: 20));

    secondLocation = tester.getCenter(find.text('Target'));
    await gesture.moveTo(secondLocation);
    await tester.pump();

    await tester.pumpWidget(new MaterialApp(
      home: new Column(
        children: <Widget>[
          new Draggable<int>(
            data: 1,
            child: new Text('Source'),
            feedback: new Text('Dragging')
          ),
        ]
      )
    ));

    expect(events, isEmpty);
    await gesture.up();
    await tester.pump();
  });

  testWidgets('Drag and drop - remove draggable', (WidgetTester tester) async {
    final List<int> accepted = <int>[];

    await tester.pumpWidget(new MaterialApp(
        home: new Column(
            children: <Widget>[
              new Draggable<int>(
                  data: 1,
                  child: new Text('Source'),
                  feedback: new Text('Dragging')
              ),
              new DragTarget<int>(
                  builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
                    return new Container(height: 100.0, child: new Text('Target'));
                  },
                  onAccept: (int data) {
                    accepted.add(data);
                  }
              ),
            ]
        )
    ));

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

    final Point firstLocation = tester.getCenter(find.text('Source'));
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

    await tester.pumpWidget(new MaterialApp(
        home: new Column(
            children: <Widget>[
              new DragTarget<int>(
                  builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
                    return new Container(height: 100.0, child: new Text('Target'));
                  },
                  onAccept: (int data) {
                    accepted.add(data);
                  }
              ),
            ]
        )
    ));

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsNothing);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

    final Point secondLocation = tester.getCenter(find.text('Target'));
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(accepted, isEmpty);
    expect(find.text('Source'), findsNothing);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, equals(<int>[1]));
    expect(find.text('Source'), findsNothing);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
  });

  testWidgets('Tap above long-press draggable works', (WidgetTester tester) async {
    final List<String> events = <String>[];

    await tester.pumpWidget(new MaterialApp(
      home: new Material(
        child: new Center(
          child: new GestureDetector(
            onTap: () {
              events.add('tap');
            },
            child: new LongPressDraggable<int>(
              feedback: new Text('Feedback'),
              child: new Text('X'),
            ),
          ),
        ),
      ),
    ));

    expect(events, isEmpty);
    await tester.tap(find.text('X'));
    expect(events, equals(<String>['tap']));
  });

  testWidgets('Drag feedback with child anchor positions correctly', (WidgetTester tester) async {
    await _testChildAnchorFeedbackPosition(tester: tester);
  });

  testWidgets('Drag feedback with child anchor within a non-global Overlay positions correctly', (WidgetTester tester) async {
    await _testChildAnchorFeedbackPosition(tester: tester, left: 100.0, top: 100.0);
  });
}

Future<Null> _testChildAnchorFeedbackPosition({WidgetTester tester, double top: 0.0, double left: 0.0}) async {
  final List<int> accepted = <int>[];
  int dragStartedCount = 0;

  await tester.pumpWidget(new Stack(children: <Widget>[
    new Positioned(
      left: left,
      top: top,
      right: 0.0,
      bottom: 0.0,
      child: new MaterialApp(
        home: new Column(
          children: <Widget>[
            new Draggable<int>(
              data: 1,
              child: new Text('Source'),
              feedback: new Text('Dragging'),
              onDragStarted: () {
                ++dragStartedCount;
              },
            ),
            new DragTarget<int>(
              builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
                return new Container(height: 100.0, child: new Text('Target'));
              },
              onAccept: (int data) {
                accepted.add(data);
              }
            ),
          ]
        )
      )
    )
  ]));

  expect(accepted, isEmpty);
  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsNothing);
  expect(find.text('Target'), findsOneWidget);
  expect(dragStartedCount, 0);

  final Point firstLocation = tester.getCenter(find.text('Source'));
  final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
  await tester.pump();

  expect(accepted, isEmpty);
  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsOneWidget);
  expect(find.text('Target'), findsOneWidget);
  expect(dragStartedCount, 1);


  final Point secondLocation = tester.getBottomRight(find.text('Target'));
  await gesture.moveTo(secondLocation);
  await tester.pump();

  expect(accepted, isEmpty);
  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsOneWidget);
  expect(find.text('Target'), findsOneWidget);
  expect(dragStartedCount, 1);

  final Point feedbackTopLeft = tester.getTopLeft(find.text('Dragging'));
  final Point sourceTopLeft = tester.getTopLeft(find.text('Source'));
  final Offset dragOffset = secondLocation - firstLocation;
  expect(feedbackTopLeft, equals(sourceTopLeft + dragOffset));
}

class DragTargetData { }

class ExtendedDragTargetData extends DragTargetData { }