draggable_test.dart 130 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Hixie's avatar
Hixie committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7 8 9
// TODO(gspencergoog): Remove this tag once this test's state leaks/test
// dependencies have been fixed.
// https://github.com/flutter/flutter/issues/85160
// Fails with "flutter test --test-randomize-ordering-seed=123"
@Tags(<String>['no-shuffle'])
10
library;
11

12 13
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
14
import 'package:flutter/rendering.dart';
15
import 'package:flutter/services.dart';
16
import 'package:flutter_test/flutter_test.dart';
17
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
Hixie's avatar
Hixie committed
18

19 20
import 'semantics_tester.dart';

Hixie's avatar
Hixie committed
21
void main() {
22
  testWidgets('Drag and drop - control test', (WidgetTester tester) async {
23
    final List<int> accepted = <int>[];
24
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
25
    int dragStartedCount = 0;
26
    int moveCount = 0;
Hixie's avatar
Hixie committed
27

28 29
    await tester.pumpWidget(MaterialApp(
      home: Column(
30
        children: <Widget>[
31
          Draggable<int>(
32
            data: 1,
33
            feedback: const Text('Dragging'),
34 35 36
            onDragStarted: () {
              ++dragStartedCount;
            },
37
            child: const Text('Source'),
38
          ),
39
          DragTarget<int>(
40
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
41
              return const SizedBox(height: 100.0, child: Text('Target'));
42
            },
43
            onMove: (_) => moveCount++,
44
            onAccept: accepted.add,
45
            onAcceptWithDetails: acceptedDetails.add,
46
          ),
47 48
        ],
      ),
49 50 51
    ));

    expect(accepted, isEmpty);
52
    expect(acceptedDetails, isEmpty);
53 54 55
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
56
    expect(dragStartedCount, 0);
57
    expect(moveCount, 0);
58

59
    final Offset firstLocation = tester.getCenter(find.text('Source'));
60
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
61
    await tester.pump();
62 63

    expect(accepted, isEmpty);
64
    expect(acceptedDetails, isEmpty);
65 66 67
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
68
    expect(dragStartedCount, 1);
69
    expect(moveCount, 0);
70

71
    final Offset secondLocation = tester.getCenter(find.text('Target'));
72 73
    await gesture.moveTo(secondLocation);
    await tester.pump();
74 75

    expect(accepted, isEmpty);
76
    expect(acceptedDetails, isEmpty);
77 78 79
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
80
    expect(dragStartedCount, 1);
81
    expect(moveCount, 1);
82

83 84
    await gesture.up();
    await tester.pump();
85 86

    expect(accepted, equals(<int>[1]));
87 88
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
89 90 91
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
92
    expect(dragStartedCount, 1);
93
    expect(moveCount, 1);
Hixie's avatar
Hixie committed
94
  });
Hixie's avatar
Hixie committed
95

96
  // Regression test for https://github.com/flutter/flutter/issues/76825
97
  testWidgets('Drag and drop - onLeave callback fires correctly with generic parameter', (WidgetTester tester) async {
98
    final Map<String,int> leftBehind = <String,int>{
99 100 101 102 103 104 105 106 107 108
      'Target 1': 0,
      'Target 2': 0,
    };

    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<int>(
            data: 1,
            feedback: Text('Dragging'),
109
            child: Text('Source'),
110 111 112
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
113
              return const SizedBox(height: 100.0, child: Text('Target 1'));
114 115 116 117 118 119 120 121 122
            },
            onLeave: (int? data) {
              if (data != null) {
                leftBehind['Target 1'] = leftBehind['Target 1']! + data;
              }
            },
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
123
              return const SizedBox(height: 100.0, child: Text('Target 2'));
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
            },
            onLeave: (int? data) {
              if (data != null) {
                leftBehind['Target 2'] = leftBehind['Target 2']! + data;
              }
            },
          ),
        ],
      ),
    ));

    expect(leftBehind['Target 1'], equals(0));
    expect(leftBehind['Target 2'], equals(0));

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

    expect(leftBehind['Target 1'], equals(0));
    expect(leftBehind['Target 2'], equals(0));

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

    expect(leftBehind['Target 1'], equals(0));
    expect(leftBehind['Target 2'], equals(0));

    final Offset thirdLocation = tester.getCenter(find.text('Target 2'));
    await gesture.moveTo(thirdLocation);
    await tester.pump();

    expect(leftBehind['Target 1'], equals(1));
    expect(leftBehind['Target 2'], equals(0));

    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(leftBehind['Target 1'], equals(1));
    expect(leftBehind['Target 2'], equals(1));

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

    expect(leftBehind['Target 1'], equals(1));
    expect(leftBehind['Target 2'], equals(1));
  });

172
  testWidgets('Drag and drop - onLeave callback fires correctly', (WidgetTester tester) async {
173
    final Map<String,int> leftBehind = <String,int>{
174 175 176 177
      'Target 1': 0,
      'Target 2': 0,
    };

178 179
    await tester.pumpWidget(MaterialApp(
      home: Column(
180 181 182
        children: <Widget>[
          const Draggable<int>(
            data: 1,
183
            feedback: Text('Dragging'),
184
            child: Text('Source'),
185
          ),
186
          DragTarget<int>(
187
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
188
              return const SizedBox(height: 100.0, child: Text('Target 1'));
189
            },
190
            onLeave: (Object? data) {
191
              if (data is int) {
192
                leftBehind['Target 1'] = leftBehind['Target 1']! + data;
193 194
              }
            },
195
          ),
196
          DragTarget<int>(
197
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
198
              return const SizedBox(height: 100.0, child: Text('Target 2'));
199
            },
200
            onLeave: (Object? data) {
201
              if (data is int) {
202
                leftBehind['Target 2'] = leftBehind['Target 2']! + data;
203 204
              }
            },
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
          ),
        ],
      ),
    ));

    expect(leftBehind['Target 1'], equals(0));
    expect(leftBehind['Target 2'], equals(0));

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

    expect(leftBehind['Target 1'], equals(0));
    expect(leftBehind['Target 2'], equals(0));

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

    expect(leftBehind['Target 1'], equals(0));
    expect(leftBehind['Target 2'], equals(0));

    final Offset thirdLocation = tester.getCenter(find.text('Target 2'));
    await gesture.moveTo(thirdLocation);
    await tester.pump();

    expect(leftBehind['Target 1'], equals(1));
    expect(leftBehind['Target 2'], equals(0));

    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(leftBehind['Target 1'], equals(1));
    expect(leftBehind['Target 2'], equals(1));

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

    expect(leftBehind['Target 1'], equals(1));
    expect(leftBehind['Target 2'], equals(1));
245
  });
246

247
  // Regression test for https://github.com/flutter/flutter/issues/76825
248
  testWidgets('Drag and drop - onMove callback fires correctly with generic parameter', (WidgetTester tester) async {
249
    final Map<String,int> targetMoveCount = <String,int>{
250 251 252 253 254 255 256 257 258 259
      'Target 1': 0,
      'Target 2': 0,
    };

    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<int>(
            data: 1,
            feedback: Text('Dragging'),
260
            child: Text('Source'),
261 262 263
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
264
              return const SizedBox(height: 100.0, child: Text('Target 1'));
265 266 267
            },
            onMove: (DragTargetDetails<int> details) {
              targetMoveCount['Target 1'] =
268
                  targetMoveCount['Target 1']! + details.data;
269 270 271 272
            },
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
273
              return const SizedBox(height: 100.0, child: Text('Target 2'));
274 275 276
            },
            onMove: (DragTargetDetails<int> details) {
              targetMoveCount['Target 2'] =
277
                  targetMoveCount['Target 2']! + details.data;
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
            },
          ),
        ],
      ),
    ));

    expect(targetMoveCount['Target 1'], equals(0));
    expect(targetMoveCount['Target 2'], equals(0));

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

    expect(targetMoveCount['Target 1'], equals(0));
    expect(targetMoveCount['Target 2'], equals(0));

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

    expect(targetMoveCount['Target 1'], equals(1));
    expect(targetMoveCount['Target 2'], equals(0));

    final Offset thirdLocation = tester.getCenter(find.text('Target 2'));
    await gesture.moveTo(thirdLocation);
    await tester.pump();

    expect(targetMoveCount['Target 1'], equals(1));
    expect(targetMoveCount['Target 2'], equals(1));

    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(targetMoveCount['Target 1'], equals(2));
    expect(targetMoveCount['Target 2'], equals(1));

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

    expect(targetMoveCount['Target 1'], equals(2));
    expect(targetMoveCount['Target 2'], equals(1));
  });

321
  testWidgets('Drag and drop - onMove callback fires correctly', (WidgetTester tester) async {
322
    final Map<String,int> targetMoveCount = <String,int>{
323 324 325 326 327 328 329 330 331 332
      'Target 1': 0,
      'Target 2': 0,
    };

    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<int>(
            data: 1,
            feedback: Text('Dragging'),
333
            child: Text('Source'),
334 335
          ),
          DragTarget<int>(
336
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
337
              return const SizedBox(height: 100.0, child: Text('Target 1'));
338 339 340 341
            },
            onMove: (DragTargetDetails<dynamic> details) {
              if (details.data is int) {
                targetMoveCount['Target 1'] =
342
                    targetMoveCount['Target 1']! + (details.data as int);
343 344 345 346
              }
            },
          ),
          DragTarget<int>(
347
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
348
              return const SizedBox(height: 100.0, child: Text('Target 2'));
349 350 351 352
            },
            onMove: (DragTargetDetails<dynamic> details) {
              if (details.data is int) {
                targetMoveCount['Target 2'] =
353
                    targetMoveCount['Target 2']! + (details.data as int);
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
              }
            },
          ),
        ],
      ),
    ));

    expect(targetMoveCount['Target 1'], equals(0));
    expect(targetMoveCount['Target 2'], equals(0));

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

    expect(targetMoveCount['Target 1'], equals(0));
    expect(targetMoveCount['Target 2'], equals(0));

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

    expect(targetMoveCount['Target 1'], equals(1));
    expect(targetMoveCount['Target 2'], equals(0));

    final Offset thirdLocation = tester.getCenter(find.text('Target 2'));
    await gesture.moveTo(thirdLocation);
    await tester.pump();

    expect(targetMoveCount['Target 1'], equals(1));
    expect(targetMoveCount['Target 2'], equals(1));

    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(targetMoveCount['Target 1'], equals(2));
    expect(targetMoveCount['Target 2'], equals(1));

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

    expect(targetMoveCount['Target 1'], equals(2));
    expect(targetMoveCount['Target 2'], equals(1));
  });

398
  testWidgets('Drag and drop - onMove is not called if moved with null data', (WidgetTester tester) async {
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
    bool onMoveCalled = false;

    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<int>(
            feedback: Text('Dragging'),
            child: Text('Source'),
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
              return const SizedBox(height: 100.0, child: Text('Target'));
            },
            onMove: (DragTargetDetails<dynamic> details) {
              onMoveCalled = true;
            },
          ),
        ],
      ),
    ));

    expect(onMoveCalled, isFalse);

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

    expect(onMoveCalled, isFalse);

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

    expect(onMoveCalled, isFalse);
    await gesture.up();
    await tester.pump();

    expect(onMoveCalled, isFalse);
  });

439
  testWidgets('Drag and drop - dragging over button', (WidgetTester tester) async {
440
    final List<String> events = <String>[];
441
    Offset firstLocation, secondLocation;
Hixie's avatar
Hixie committed
442

443 444
    await tester.pumpWidget(MaterialApp(
      home: Column(
445
        children: <Widget>[
446
          const Draggable<int>(
447
            data: 1,
448
            feedback: Text('Dragging'),
449
            child: Text('Source'),
450
          ),
451
          Stack(
452
            children: <Widget>[
453
              GestureDetector(
454 455 456 457
                behavior: HitTestBehavior.opaque,
                onTap: () {
                  events.add('tap');
                },
458
                child: const Text('Button'),
459 460
              ),
              DragTarget<int>(
461
                builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
462 463
                  return const IgnorePointer(
                    child: Text('Target'),
464 465
                  );
                },
466
                onAccept: (int? data) {
467 468
                  events.add('drop');
                },
469 470 471
                onAcceptWithDetails: (DragTargetDetails<int> _) {
                  events.add('details');
                },
472 473
              ),
            ],
474
          ),
475 476
        ],
      ),
477 478 479 480 481 482 483 484 485 486 487
    ));

    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);
488
    await tester.tap(find.text('Button'));
489 490 491 492
    expect(events, equals(<String>['tap']));
    events.clear();

    expect(events, isEmpty);
493
    await tester.tap(find.text('Target'), warnIfMissed: false); // (inside IgnorePointer)
494 495 496 497 498 499
    expect(events, equals(<String>['tap']));
    events.clear();

    // drag and drop

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

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

    expect(events, isEmpty);
508 509
    await gesture.up();
    await tester.pump();
510
    expect(events, equals(<String>['drop', 'details']));
511 512 513 514 515
    events.clear();

    // drag and tap and drop

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

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

    expect(events, isEmpty);
524
    await tester.tap(find.text('Button'));
525
    await tester.tap(find.text('Target'), warnIfMissed: false); // (inside IgnorePointer)
526 527
    await gesture.up();
    await tester.pump();
528
    expect(events, equals(<String>['tap', 'tap', 'drop', 'details']));
529
    events.clear();
530 531
  });

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

536 537
    await tester.pumpWidget(MaterialApp(
      home: Column(
538
        children: <Widget>[
539
          Draggable<int>(
540
            data: 1,
541
            feedback: const Text('Dragging'),
542
            child: GestureDetector(
543 544 545
              behavior: HitTestBehavior.opaque,
              onTap: () {
                events.add('tap');
546
              },
547
              child: const Text('Button'),
548
            ),
549
          ),
550
          DragTarget<int>(
551
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
552
              return const Text('Target');
553
            },
554
            onAccept: (int? data) {
555
              events.add('drop');
556
            },
557 558 559
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
560
          ),
561 562
        ],
      ),
563 564 565 566 567 568 569
    ));

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

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

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

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

    expect(events, isEmpty);
583 584
    await gesture.up();
    await tester.pump();
585
    expect(events, equals(<String>['drop', 'details']));
586
    events.clear();
587 588
  });

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

593 594
    await tester.pumpWidget(MaterialApp(
      home: Column(
595
        children: <Widget>[
596
          const LongPressDraggable<int>(
597
            data: 1,
598
            feedback: Text('Dragging'),
599
            child: Text('Source'),
600
          ),
601
          DragTarget<int>(
602
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
603
              return const Text('Target');
604
            },
605
            onAccept: (int? data) {
606
              events.add('drop');
607
            },
608 609 610
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
611
          ),
612 613
        ],
      ),
614 615 616 617 618 619 620
    ));

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

    expect(events, isEmpty);
621
    await tester.tap(find.text('Source'));
622 623 624
    expect(events, isEmpty);

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

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

    expect(events, isEmpty);
633 634
    await gesture.up();
    await tester.pump();
635
    expect(events, isEmpty);
636 637
  });

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

642 643
    await tester.pumpWidget(MaterialApp(
      home: Column(
644
        children: <Widget>[
645
          const Draggable<int>(
646
            data: 1,
647
            feedback: Text('Dragging'),
648
            child: Text('Source'),
649
          ),
650
          DragTarget<int>(
651
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
652
              return const Text('Target');
653
            },
654
            onAccept: (int? data) {
655
              events.add('drop');
656
            },
657 658 659
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
660
          ),
661 662
        ],
      ),
663
    ));
664

665 666 667
    expect(events, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
668

669
    expect(events, isEmpty);
670
    await tester.tap(find.text('Source'));
671
    expect(events, isEmpty);
672

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

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

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

683
    expect(events, isEmpty);
684 685
    await gesture.up();
    await tester.pump();
686
    expect(events, equals(<String>['drop', 'details']));
687 688
  });

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

693 694
    await tester.pumpWidget(MaterialApp(
      home: ListView(
695
        dragStartBehavior: DragStartBehavior.down,
696
        children: <Widget>[
697
          DragTarget<int>(
698
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
699
              return const Text('Target');
700
            },
701
            onAccept: (int? data) {
702
              events.add('drop $data');
703
            },
704 705 706
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
707
          ),
708
          Container(height: 400.0),
709
          const Draggable<int>(
710
            data: 1,
711
            feedback: Text('Dragging'),
712
            affinity: Axis.horizontal,
713
            child: Text('H'),
714
          ),
715
          const Draggable<int>(
716
            data: 2,
717
            feedback: Text('Dragging'),
718
            affinity: Axis.vertical,
719
            child: Text('V'),
720
          ),
721 722 723 724
          Container(height: 500.0),
          Container(height: 500.0),
          Container(height: 500.0),
          Container(height: 500.0),
725 726
        ],
      ),
727 728 729 730 731 732 733 734 735 736 737
    ));

    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'));
738 739 740 741 742 743
    TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
    await gesture.up();
    await tester.pump();
744
    expect(events, equals(<String>['drop 2', 'details']));
745
    expect(tester.getCenter(find.text('Target')).dy, greaterThan(0.0));
746 747 748 749 750 751 752
    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'));
753 754 755 756 757 758 759 760
    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();
761
    expect(events, equals(<String>['drop 1', 'details']));
762
    expect(tester.getCenter(find.text('Target')).dy, greaterThan(0.0));
763 764 765 766 767 768 769 770
    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'));
771 772 773 774 775 776 777 778
    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();
779
    expect(events, equals(<String>['drop 2', 'details']));
780
    expect(tester.getCenter(find.text('Target')).dy, greaterThan(0.0));
781 782 783 784 785 786 787
    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'));
788 789 790 791 792 793
    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();
794
    expect(events, equals(<String>[]));
795
    expect(find.text('Target'), findsNothing);
796 797
    events.clear();
  });
798

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

803 804
    await tester.pumpWidget(MaterialApp(
      home: ListView(
805
        dragStartBehavior: DragStartBehavior.down,
806 807
        scrollDirection: Axis.horizontal,
        children: <Widget>[
808
          DragTarget<int>(
809
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
810
              return const Text('Target');
811
            },
812
            onAccept: (int? data) {
813
              events.add('drop $data');
814
            },
815 816 817
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
818
          ),
819
          Container(width: 400.0),
820
          const Draggable<int>(
821
            data: 1,
822
            feedback: Text('Dragging'),
823
            affinity: Axis.horizontal,
824
            child: Text('H'),
825
          ),
826
          const Draggable<int>(
827
            data: 2,
828
            feedback: Text('Dragging'),
829
            affinity: Axis.vertical,
830
            child: Text('V'),
831
          ),
832 833 834 835
          Container(width: 500.0),
          Container(width: 500.0),
          Container(width: 500.0),
          Container(width: 500.0),
836 837
        ],
      ),
838 839 840 841 842 843 844 845 846 847 848
    ));

    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'));
849 850 851 852 853 854
    TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
    await gesture.up();
    await tester.pump();
855
    expect(events, equals(<String>['drop 1', 'details']));
856
    expect(tester.getCenter(find.text('Target')).dx, greaterThan(0.0));
857 858 859 860 861 862 863
    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'));
864 865 866 867 868 869 870 871
    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();
872
    expect(events, equals(<String>['drop 2', 'details']));
873
    expect(tester.getCenter(find.text('Target')).dx, greaterThan(0.0));
874 875 876 877 878 879 880 881
    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'));
882 883 884 885 886 887 888 889
    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();
890
    expect(events, equals(<String>['drop 1', 'details']));
891
    expect(tester.getCenter(find.text('Target')).dx, greaterThan(0.0));
892 893 894 895 896 897 898
    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'));
899 900 901 902 903 904
    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();
905
    expect(events, equals(<String>[]));
906
    expect(find.text('Target'), findsNothing);
907 908
    events.clear();
  });
909

910 911 912 913
  group('Drag and drop - Draggables with a set axis only move along that axis', () {
    final List<String> events = <String>[];

    Widget build() {
914 915
      return MaterialApp(
        home: ListView(
916 917
          scrollDirection: Axis.horizontal,
          children: <Widget>[
918
            DragTarget<int>(
919
              builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
920 921
                return const Text('Target');
              },
922
              onAccept: (int? data) {
923
                events.add('drop $data');
924
              },
925 926 927
              onAcceptWithDetails: (DragTargetDetails<int> _) {
                events.add('details');
              },
928
            ),
929
            Container(width: 400.0),
930 931
            const Draggable<int>(
              data: 1,
932 933
              feedback: Text('H'),
              childWhenDragging: SizedBox(),
934
              axis: Axis.horizontal,
935
              child: Text('H'),
936 937 938
            ),
            const Draggable<int>(
              data: 2,
939 940
              feedback: Text('V'),
              childWhenDragging: SizedBox(),
941
              axis: Axis.vertical,
942
              child: Text('V'),
943 944 945
            ),
            const Draggable<int>(
              data: 3,
946 947
              feedback: Text('N'),
              childWhenDragging: SizedBox(),
948
              child: Text('N'),
949
            ),
950 951 952 953
            Container(width: 500.0),
            Container(width: 500.0),
            Container(width: 500.0),
            Container(width: 500.0),
954 955 956 957
          ],
        ),
      );
    }
958
    testWidgets('Null axis draggable moves along all axes', (WidgetTester tester) async {
959 960 961 962 963 964 965 966 967 968 969 970 971 972
      await tester.pumpWidget(build());
      final Offset firstLocation = tester.getTopLeft(find.text('N'));
      final Offset secondLocation = firstLocation + const Offset(300.0, 300.0);
      final Offset thirdLocation = firstLocation + const Offset(-300.0, -300.0);
      final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
      await tester.pump();
      await gesture.moveTo(secondLocation);
      await tester.pump();
      expect(tester.getTopLeft(find.text('N')), secondLocation);
      await gesture.moveTo(thirdLocation);
      await tester.pump();
      expect(tester.getTopLeft(find.text('N')), thirdLocation);
    });

973
    testWidgets('Horizontal axis draggable moves horizontally', (WidgetTester tester) async {
974 975 976 977 978 979 980 981 982 983 984 985 986 987
      await tester.pumpWidget(build());
      final Offset firstLocation = tester.getTopLeft(find.text('H'));
      final Offset secondLocation = firstLocation + const Offset(300.0, 0.0);
      final Offset thirdLocation = firstLocation + const Offset(-300.0, 0.0);
      final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
      await tester.pump();
      await gesture.moveTo(secondLocation);
      await tester.pump();
      expect(tester.getTopLeft(find.text('H')), secondLocation);
      await gesture.moveTo(thirdLocation);
      await tester.pump();
      expect(tester.getTopLeft(find.text('H')), thirdLocation);
    });

988
    testWidgets('Horizontal axis draggable does not move vertically', (WidgetTester tester) async {
989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
      await tester.pumpWidget(build());
      final Offset firstLocation = tester.getTopLeft(find.text('H'));
      final Offset secondDragLocation = firstLocation + const Offset(300.0, 200.0);
      // The horizontal drag widget won't scroll vertically.
      final Offset secondWidgetLocation = firstLocation + const Offset(300.0, 0.0);
      final Offset thirdDragLocation = firstLocation + const Offset(-300.0, -200.0);
      final Offset thirdWidgetLocation = firstLocation + const Offset(-300.0, 0.0);
      final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
      await tester.pump();
      await gesture.moveTo(secondDragLocation);
      await tester.pump();
      expect(tester.getTopLeft(find.text('H')), secondWidgetLocation);
      await gesture.moveTo(thirdDragLocation);
      await tester.pump();
      expect(tester.getTopLeft(find.text('H')), thirdWidgetLocation);
    });

1006
    testWidgets('Vertical axis draggable moves vertically', (WidgetTester tester) async {
1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
      await tester.pumpWidget(build());
      final Offset firstLocation = tester.getTopLeft(find.text('V'));
      final Offset secondLocation = firstLocation + const Offset(0.0, 300.0);
      final Offset thirdLocation = firstLocation + const Offset(0.0, -300.0);
      final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
      await tester.pump();
      await gesture.moveTo(secondLocation);
      await tester.pump();
      expect(tester.getTopLeft(find.text('V')), secondLocation);
      await gesture.moveTo(thirdLocation);
      await tester.pump();
      expect(tester.getTopLeft(find.text('V')), thirdLocation);
    });

1021
    testWidgets('Vertical axis draggable does not move horizontally', (WidgetTester tester) async {
1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
      await tester.pumpWidget(build());
      final Offset firstLocation = tester.getTopLeft(find.text('V'));
      final Offset secondDragLocation = firstLocation + const Offset(200.0, 300.0);
      // The vertical drag widget won't scroll horizontally.
      final Offset secondWidgetLocation = firstLocation + const Offset(0.0, 300.0);
      final Offset thirdDragLocation = firstLocation + const Offset(-200.0, -300.0);
      final Offset thirdWidgetLocation = firstLocation + const Offset(0.0, -300.0);
      final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
      await tester.pump();
      await gesture.moveTo(secondDragLocation);
      await tester.pump();
      expect(tester.getTopLeft(find.text('V')), secondWidgetLocation);
      await gesture.moveTo(thirdDragLocation);
      await tester.pump();
      expect(tester.getTopLeft(find.text('V')), thirdWidgetLocation);
    });
  });
1039

1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
  group('Drag and drop - onDragUpdate called if draggable moves along a set axis', () {
    int updated = 0;
    Offset dragDelta = Offset.zero;

    setUp(() {
      updated = 0;
      dragDelta = Offset.zero;
    });

    Widget build() {
      return MaterialApp(
        home: Column(
          children: <Widget>[
            Draggable<int>(
              data: 1,
              feedback: const Text('Dragging'),
              onDragUpdate: (DragUpdateDetails details) {
                dragDelta += details.delta;
                updated++;
              },
1060
              child: const Text('Source'),
1061 1062 1063 1064 1065 1066 1067 1068 1069
            ),
            Draggable<int>(
              data: 2,
              feedback: const Text('Vertical Dragging'),
              onDragUpdate: (DragUpdateDetails details) {
                dragDelta += details.delta;
                updated++;
              },
              axis: Axis.vertical,
1070
              child: const Text('Vertical Source'),
1071 1072 1073 1074 1075 1076 1077 1078 1079
            ),
            Draggable<int>(
              data: 3,
              feedback: const Text('Horizontal Dragging'),
              onDragUpdate: (DragUpdateDetails details) {
                dragDelta += details.delta;
                updated++;
              },
              axis: Axis.horizontal,
1080
              child: const Text('Horizontal Source'),
1081 1082 1083 1084 1085 1086
            ),
          ],
        ),
      );
    }

1087
    testWidgets('Null axis onDragUpdate called only if draggable moves in any direction', (WidgetTester tester) async {
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
      await tester.pumpWidget(build());

      expect(updated, 0);
      expect(find.text('Source'), findsOneWidget);
      expect(find.text('Dragging'), findsNothing);

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

      expect(updated, 0);
      expect(find.text('Source'), findsOneWidget);
      expect(find.text('Dragging'), findsOneWidget);

      await gesture.moveBy(const Offset(10, 10));
      await tester.pump();

      expect(updated, 1);

      await gesture.moveBy(Offset.zero);
      await tester.pump();

      expect(updated, 1);

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

      expect(updated, 1);
      expect(find.text('Source'), findsOneWidget);
      expect(find.text('Dragging'), findsNothing);
      expect(dragDelta.dx, 10);
      expect(dragDelta.dy, 10);
    });

1122
    testWidgets('Vertical axis onDragUpdate only called if draggable moves vertical', (WidgetTester tester) async {
1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156
      await tester.pumpWidget(build());

      expect(updated, 0);
      expect(find.text('Vertical Source'), findsOneWidget);
      expect(find.text('Vertical Dragging'), findsNothing);

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

      expect(updated, 0);
      expect(find.text('Vertical Source'), findsOneWidget);
      expect(find.text('Vertical Dragging'), findsOneWidget);

      await gesture.moveBy(const Offset(0, 10));
      await tester.pump();

      expect(updated, 1);

      await gesture.moveBy(const Offset(10 , 0));
      await tester.pump();

      expect(updated, 1);

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

      expect(updated, 1);
      expect(find.text('Vertical Source'), findsOneWidget);
      expect(find.text('Vertical Dragging'), findsNothing);
      expect(dragDelta.dx, 0);
      expect(dragDelta.dy, 10);
    });

1157
    testWidgets('Horizontal axis onDragUpdate only called if draggable moves horizontal', (WidgetTester tester) async {
1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191
      await tester.pumpWidget(build());

      expect(updated, 0);
      expect(find.text('Horizontal Source'), findsOneWidget);
      expect(find.text('Horizontal Dragging'), findsNothing);

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

      expect(updated, 0);
      expect(find.text('Horizontal Source'), findsOneWidget);
      expect(find.text('Horizontal Dragging'), findsOneWidget);

      await gesture.moveBy(const Offset(0, 10));
      await tester.pump();

      expect(updated, 0);

      await gesture.moveBy(const Offset(10 , 0));
      await tester.pump();

      expect(updated, 1);

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

      expect(updated, 1);
      expect(find.text('Horizontal Source'), findsOneWidget);
      expect(find.text('Horizontal Dragging'), findsNothing);
      expect(dragDelta.dx, 10);
      expect(dragDelta.dy, 0);
    });
  });
1192

1193
  testWidgets('Drag and drop - onDraggableCanceled not called if dropped on accepting target', (WidgetTester tester) async {
1194
    final List<int> accepted = <int>[];
1195
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1196
    bool onDraggableCanceledCalled = false;
1197

1198 1199
    await tester.pumpWidget(MaterialApp(
      home: Column(
1200
        children: <Widget>[
1201
          Draggable<int>(
1202
            data: 1,
1203
            feedback: const Text('Dragging'),
1204 1205
            onDraggableCanceled: (Velocity velocity, Offset offset) {
              onDraggableCanceledCalled = true;
1206
            },
1207
            child: const Text('Source'),
1208
          ),
1209
          DragTarget<int>(
1210
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1211
              return const SizedBox(height: 100.0, child: Text('Target'));
1212
            },
1213
            onAccept: accepted.add,
1214
            onAcceptWithDetails: acceptedDetails.add,
1215
          ),
1216 1217
        ],
      ),
1218 1219 1220
    ));

    expect(accepted, isEmpty);
1221
    expect(acceptedDetails, isEmpty);
1222 1223 1224 1225 1226
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1227
    final Offset firstLocation = tester.getCenter(find.text('Source'));
1228
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
1229
    await tester.pump();
1230 1231

    expect(accepted, isEmpty);
1232
    expect(acceptedDetails, isEmpty);
1233 1234 1235 1236 1237
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1238
    final Offset secondLocation = tester.getCenter(find.text('Target'));
1239 1240
    await gesture.moveTo(secondLocation);
    await tester.pump();
1241 1242

    expect(accepted, isEmpty);
1243
    expect(acceptedDetails, isEmpty);
1244 1245 1246 1247 1248
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1249 1250
    await gesture.up();
    await tester.pump();
1251 1252

    expect(accepted, equals(<int>[1]));
1253 1254
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
1255 1256 1257 1258
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);
1259 1260
  });

1261
  testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target', (WidgetTester tester) async {
1262
    final List<int> accepted = <int>[];
1263
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1264
    bool onDraggableCanceledCalled = false;
1265 1266
    late Velocity onDraggableCanceledVelocity;
    late Offset onDraggableCanceledOffset;
1267

1268 1269
    await tester.pumpWidget(MaterialApp(
      home: Column(
1270
        children: <Widget>[
1271
          Draggable<int>(
1272
            data: 1,
1273
            feedback: const Text('Dragging'),
1274 1275 1276 1277
            onDraggableCanceled: (Velocity velocity, Offset offset) {
              onDraggableCanceledCalled = true;
              onDraggableCanceledVelocity = velocity;
              onDraggableCanceledOffset = offset;
1278
            },
1279
            child: const Text('Source'),
1280
          ),
1281
          DragTarget<int>(
1282
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1283
              return const SizedBox(
1284
                height: 100.0,
1285
                child: Text('Target'),
1286 1287
              );
            },
1288
            onWillAccept: (int? data) => false,
1289
            onAccept: accepted.add,
1290
            onAcceptWithDetails: acceptedDetails.add,
1291
          ),
1292 1293
        ],
      ),
1294 1295 1296
    ));

    expect(accepted, isEmpty);
1297
    expect(acceptedDetails, isEmpty);
1298 1299 1300 1301 1302
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1303
    final Offset firstLocation = tester.getTopLeft(find.text('Source'));
1304
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
1305
    await tester.pump();
1306 1307

    expect(accepted, isEmpty);
1308
    expect(acceptedDetails, isEmpty);
1309 1310 1311 1312 1313
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1314
    final Offset secondLocation = tester.getCenter(find.text('Target'));
1315 1316
    await gesture.moveTo(secondLocation);
    await tester.pump();
1317 1318

    expect(accepted, isEmpty);
1319
    expect(acceptedDetails, isEmpty);
1320 1321 1322 1323 1324
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1325 1326
    await gesture.up();
    await tester.pump();
1327 1328

    expect(accepted, isEmpty);
1329
    expect(acceptedDetails, isEmpty);
1330 1331 1332 1333 1334
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isTrue);
    expect(onDraggableCanceledVelocity, equals(Velocity.zero));
1335
    expect(onDraggableCanceledOffset, equals(Offset(secondLocation.dx, secondLocation.dy)));
1336
  });
1337

1338
  testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with details', (WidgetTester tester) async {
1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414
    final List<int> accepted = <int>[];
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
    bool onDraggableCanceledCalled = false;
    late Velocity onDraggableCanceledVelocity;
    late Offset onDraggableCanceledOffset;

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

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

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

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

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

    expect(accepted, isEmpty);
    expect(acceptedDetails, 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(acceptedDetails, 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(Offset(secondLocation.dx, secondLocation.dy)));
  });

1415
  testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with correct velocity', (WidgetTester tester) async {
1416
    final List<int> accepted = <int>[];
1417
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1418
    bool onDraggableCanceledCalled = false;
1419 1420
    late Velocity onDraggableCanceledVelocity;
    late Offset onDraggableCanceledOffset;
1421

1422 1423 1424
    await tester.pumpWidget(MaterialApp(
      home: Column(children: <Widget>[
        Draggable<int>(
1425
          data: 1,
1426
          feedback: const Text('Source'),
1427 1428 1429 1430
          onDraggableCanceled: (Velocity velocity, Offset offset) {
            onDraggableCanceledCalled = true;
            onDraggableCanceledVelocity = velocity;
            onDraggableCanceledOffset = offset;
1431
          },
1432
          child: const Text('Source'),
1433
        ),
1434
        DragTarget<int>(
1435
          builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1436
            return const SizedBox(height: 100.0, child: Text('Target'));
1437
          },
1438
          onWillAccept: (int? data) => false,
1439
          onAccept: accepted.add,
1440 1441
          onAcceptWithDetails: acceptedDetails.add,
        ),
1442 1443
      ]),
    ));
1444 1445

    expect(accepted, isEmpty);
1446
    expect(acceptedDetails, isEmpty);
1447 1448 1449 1450 1451
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1452
    final Offset flingStart = tester.getTopLeft(find.text('Source'));
1453
    await tester.flingFrom(flingStart, const Offset(0.0, 100.0), 1000.0);
1454
    await tester.pump();
1455 1456

    expect(accepted, isEmpty);
1457
    expect(acceptedDetails, isEmpty);
1458 1459 1460 1461 1462 1463
    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));
1464
    expect(onDraggableCanceledOffset, equals(Offset(flingStart.dx, flingStart.dy) + const Offset(0.0, 100.0)));
1465
  });
1466

1467
  testWidgets('Drag and drop - onDragEnd not called if dropped on non-accepting target', (WidgetTester tester) async {
1468
    final List<int> accepted = <int>[];
1469
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1470
    bool onDragEndCalled = false;
1471
    late DraggableDetails onDragEndDraggableDetails;
1472 1473 1474 1475 1476 1477 1478 1479 1480 1481
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
            data: 1,
            feedback: const Text('Dragging'),
            onDragEnd: (DraggableDetails details) {
              onDragEndCalled = true;
              onDragEndDraggableDetails = details;
            },
1482
            child: const Text('Source'),
1483 1484
          ),
          DragTarget<int>(
1485
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1486
              return const SizedBox(height: 100.0, child: Text('Target'));
1487
            },
1488
            onWillAccept: (int? data) => false,
1489
            onAccept: accepted.add,
1490
            onAcceptWithDetails: acceptedDetails.add,
1491 1492 1493 1494 1495 1496
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);
1497
    expect(acceptedDetails, isEmpty);
1498 1499 1500 1501 1502 1503 1504 1505 1506 1507
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isFalse);

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

    expect(accepted, isEmpty);
1508
    expect(acceptedDetails, isEmpty);
1509 1510 1511 1512 1513 1514 1515 1516 1517 1518
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isFalse);

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

    expect(accepted, isEmpty);
1519
    expect(acceptedDetails, isEmpty);
1520 1521 1522 1523 1524 1525 1526 1527 1528
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isFalse);

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

    expect(accepted, isEmpty);
1529
    expect(acceptedDetails, isEmpty);
1530 1531 1532 1533 1534 1535 1536
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isTrue);
    expect(onDragEndDraggableDetails, isNotNull);
    expect(onDragEndDraggableDetails.wasAccepted, isFalse);
    expect(onDragEndDraggableDetails.velocity, equals(Velocity.zero));
1537 1538 1539 1540
    expect(
      onDragEndDraggableDetails.offset,
      equals(Offset(secondLocation.dx, secondLocation.dy - firstLocation.dy)),
    );
1541 1542
  });

1543
  testWidgets('Drag and drop - onDragEnd not called if dropped on non-accepting target with details', (WidgetTester tester) async {
1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618
    final List<int> accepted = <int>[];
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
    bool onDragEndCalled = false;
    late DraggableDetails onDragEndDraggableDetails;
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
            data: 1,
            feedback: const Text('Dragging'),
            onDragEnd: (DraggableDetails details) {
              onDragEndCalled = true;
              onDragEndDraggableDetails = details;
            },
            child: const Text('Source'),
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
              return const SizedBox(height: 100.0, child: Text('Target'));
            },
            onWillAcceptWithDetails: (DragTargetDetails<int> data) => false,
            onAccept: accepted.add,
            onAcceptWithDetails: acceptedDetails.add,
          ),
        ],
      ),
    ));

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

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

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

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

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

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

    expect(accepted, isEmpty);
    expect(acceptedDetails, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isTrue);
    expect(onDragEndDraggableDetails, isNotNull);
    expect(onDragEndDraggableDetails.wasAccepted, isFalse);
    expect(onDragEndDraggableDetails.velocity, equals(Velocity.zero));
    expect(
      onDragEndDraggableDetails.offset,
      equals(Offset(secondLocation.dx, secondLocation.dy - firstLocation.dy)),
    );
  });

1619
  testWidgets('Drag and drop - DragTarget rebuilds with and without rejected data when a rejected draggable enters and leaves', (WidgetTester tester) async {
1620 1621 1622 1623 1624 1625
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<int>(
            data: 1,
            feedback: Text('Dragging'),
1626
            child: Text('Source'),
1627 1628
          ),
          DragTarget<int>(
1629
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1630
              return SizedBox(
1631 1632 1633 1634 1635 1636
                height: 100.0,
                child: rejects.isNotEmpty
                    ? const Text('Rejected')
                    : const Text('Target'),
              );
            },
1637
            onWillAccept: (int? data) => false,
1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648
          ),
        ],
      ),
    ));

    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('Rejected'), findsNothing);

    final Offset firstLocation = tester.getTopLeft(find.text('Source'));
    final TestGesture gesture =
1649
        await tester.startGesture(firstLocation, pointer: 7);
1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672
    await tester.pump();

    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('Rejected'), findsNothing);

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

    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsNothing);
    expect(find.text('Rejected'), findsOneWidget);

    await gesture.moveTo(firstLocation);
    await tester.pump();

    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('Rejected'), findsNothing);
  });


1673
  testWidgets('Drag and drop - Can drag and drop over a non-accepting target multiple times', (WidgetTester tester) async {
1674 1675 1676 1677 1678 1679 1680 1681 1682 1683
    int numberOfTimesOnDraggableCanceledCalled = 0;
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
            data: 1,
            feedback: const Text('Dragging'),
          onDraggableCanceled: (Velocity velocity, Offset offset) {
            numberOfTimesOnDraggableCanceledCalled++;
          },
1684
            child: const Text('Source'),
1685 1686
          ),
          DragTarget<int>(
1687
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1688
              return SizedBox(
1689 1690 1691 1692 1693 1694
                height: 100.0,
                child: rejects.isNotEmpty
                    ? const Text('Rejected')
                    : const Text('Target'),
              );
            },
1695
            onWillAccept: (int? data) => false,
1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706
          ),
        ],
      ),
    ));

    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('Rejected'), findsNothing);

    final Offset firstLocation = tester.getTopLeft(find.text('Source'));
    final TestGesture gesture =
1707
        await tester.startGesture(firstLocation, pointer: 7);
1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731
    await tester.pump();

    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('Rejected'), findsNothing);

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

    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsNothing);
    expect(find.text('Rejected'), findsOneWidget);

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

    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('Rejected'), findsNothing);
    expect(numberOfTimesOnDraggableCanceledCalled, 1);

    // Drag and drop the Draggable onto the Target a second time.
    final TestGesture secondGesture =
1732
        await tester.startGesture(firstLocation, pointer: 7);
1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754
    await tester.pump();

    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('Rejected'), findsNothing);

    await secondGesture.moveTo(secondLocation);
    await tester.pump();

    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsNothing);
    expect(find.text('Rejected'), findsOneWidget);

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

    expect(numberOfTimesOnDraggableCanceledCalled, 2);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(find.text('Rejected'), findsNothing);
  });

1755
  testWidgets('Drag and drop - onDragCompleted not called if dropped on non-accepting target', (WidgetTester tester) async {
1756
    final List<int> accepted = <int>[];
1757
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1758 1759
    bool onDragCompletedCalled = false;

1760 1761
    await tester.pumpWidget(MaterialApp(
      home: Column(
1762
        children: <Widget>[
1763
          Draggable<int>(
1764 1765 1766 1767
            data: 1,
            feedback: const Text('Dragging'),
            onDragCompleted: () {
              onDragCompletedCalled = true;
1768
            },
1769
            child: const Text('Source'),
1770
          ),
1771
          DragTarget<int>(
1772
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1773
              return const SizedBox(
1774
                height: 100.0,
1775
                child: Text('Target'),
1776 1777
              );
            },
1778
            onWillAccept: (int? data) => false,
1779
            onAccept: accepted.add,
1780
            onAcceptWithDetails: acceptedDetails.add,
1781
          ),
1782 1783
        ],
      ),
1784 1785 1786
    ));

    expect(accepted, isEmpty);
1787
    expect(acceptedDetails, isEmpty);
1788 1789 1790 1791 1792 1793 1794 1795 1796 1797
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);

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

    expect(accepted, isEmpty);
1798
    expect(acceptedDetails, isEmpty);
1799 1800 1801 1802 1803 1804 1805 1806 1807 1808
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);

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

    expect(accepted, isEmpty);
1809
    expect(acceptedDetails, isEmpty);
1810 1811 1812 1813 1814 1815 1816 1817 1818
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);

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

    expect(accepted, isEmpty);
1819
    expect(acceptedDetails, isEmpty);
1820 1821 1822 1823 1824 1825
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);
  });

1826
  testWidgets('Drag and drop - onDragCompleted not called if dropped on non-accepting target with details', (WidgetTester tester) async {
1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896
    final List<int> accepted = <int>[];
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
    bool onDragCompletedCalled = false;

    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
            data: 1,
            feedback: const Text('Dragging'),
            onDragCompleted: () {
              onDragCompletedCalled = true;
            },
            child: const Text('Source'),
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
              return const SizedBox(
                height: 100.0,
                child: Text('Target'),
              );
            },
            onWillAcceptWithDetails: (DragTargetDetails<int> data) => false,
            onAccept: accepted.add,
            onAcceptWithDetails: acceptedDetails.add,
          ),
        ],
      ),
    ));

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

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

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

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

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

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

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

1897
  testWidgets('Drag and drop - onDragEnd called if dropped on accepting target', (WidgetTester tester) async {
1898
    final List<int> accepted = <int>[];
1899
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1900
    bool onDragEndCalled = false;
1901
    late DraggableDetails onDragEndDraggableDetails;
1902 1903 1904 1905 1906 1907 1908 1909 1910 1911
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
            data: 1,
            feedback: const Text('Dragging'),
            onDragEnd: (DraggableDetails details) {
              onDragEndCalled = true;
              onDragEndDraggableDetails = details;
            },
1912
            child: const Text('Source'),
1913 1914
          ),
          DragTarget<int>(
1915
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1916
              return const SizedBox(height: 100.0, child: Text('Target'));
1917
            },
1918
            onAccept: accepted.add,
1919
            onAcceptWithDetails: acceptedDetails.add,
1920 1921 1922 1923 1924 1925
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);
1926
    expect(acceptedDetails, isEmpty);
1927 1928 1929 1930 1931 1932 1933 1934 1935 1936
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isFalse);

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

    expect(accepted, isEmpty);
1937
    expect(acceptedDetails, isEmpty);
1938 1939 1940 1941 1942 1943 1944 1945 1946 1947
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isFalse);

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

    expect(accepted, isEmpty);
1948
    expect(acceptedDetails, isEmpty);
1949 1950 1951 1952 1953 1954 1955 1956 1957
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isFalse);

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

    final Offset droppedLocation = tester.getTopLeft(find.text('Target'));
1958 1959
    final Offset expectedDropOffset = Offset(droppedLocation.dx, secondLocation.dy - firstLocation.dy);

1960
    expect(accepted, equals(<int>[1]));
1961 1962
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
1963 1964 1965 1966 1967 1968 1969
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isTrue);
    expect(onDragEndDraggableDetails, isNotNull);
    expect(onDragEndDraggableDetails.wasAccepted, isTrue);
    expect(onDragEndDraggableDetails.velocity, equals(Velocity.zero));
1970
    expect(onDragEndDraggableDetails.offset, equals(expectedDropOffset));
1971 1972
  });

1973
  testWidgets('DragTarget does not call onDragEnd when remove from the tree', (WidgetTester tester) async {
1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985
    final List<String> events = <String>[];
    Offset firstLocation, secondLocation;
    int timesOnDragEndCalled = 0;
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
              data: 1,
              feedback: const Text('Dragging'),
              onDragEnd: (DraggableDetails details) {
                timesOnDragEndCalled++;
              },
1986
              child: const Text('Source'),
1987 1988
          ),
          DragTarget<int>(
1989
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1990 1991
              return const Text('Target');
            },
1992
            onAccept: (int? data) {
1993 1994
              events.add('drop');
            },
1995 1996 1997
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
          ),
        ],
      ),
    ));

    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();

2021
    await tester.pumpWidget(const MaterialApp(
2022
        home: Column(
2023
            children: <Widget>[
2024 2025
              Draggable<int>(
                  data: 1,
2026
                  feedback: Text('Dragging'),
2027
                  child: Text('Source'),
2028
              ),
2029 2030
            ],
        ),
2031 2032 2033 2034 2035 2036 2037 2038
    ));

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

2039
  testWidgets('Drag and drop - onDragCompleted called if dropped on accepting target', (WidgetTester tester) async {
2040
    final List<int> accepted = <int>[];
2041
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
2042 2043
    bool onDragCompletedCalled = false;

2044 2045
    await tester.pumpWidget(MaterialApp(
      home: Column(
2046
        children: <Widget>[
2047
          Draggable<int>(
2048 2049 2050 2051
            data: 1,
            feedback: const Text('Dragging'),
            onDragCompleted: () {
              onDragCompletedCalled = true;
2052
            },
2053
            child: const Text('Source'),
2054
          ),
2055
          DragTarget<int>(
2056
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2057
              return const SizedBox(height: 100.0, child: Text('Target'));
2058
            },
2059
            onAccept: accepted.add,
2060
            onAcceptWithDetails: acceptedDetails.add,
2061
          ),
2062 2063
        ],
      ),
2064 2065 2066
    ));

    expect(accepted, isEmpty);
2067
    expect(acceptedDetails, isEmpty);
2068 2069 2070 2071 2072 2073 2074 2075 2076 2077
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);

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

    expect(accepted, isEmpty);
2078
    expect(acceptedDetails, isEmpty);
2079 2080 2081 2082 2083 2084 2085 2086 2087 2088
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);

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

    expect(accepted, isEmpty);
2089
    expect(acceptedDetails, isEmpty);
2090 2091 2092 2093 2094 2095 2096 2097 2098
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);

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

    expect(accepted, equals(<int>[1]));
2099 2100
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
2101 2102 2103 2104 2105 2106
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isTrue);
  });

2107
  testWidgets('Drag and drop - allow pass through of unaccepted data test', (WidgetTester tester) async {
2108
    final List<int> acceptedInts = <int>[];
2109
    final List<DragTargetDetails<int>> acceptedIntsDetails = <DragTargetDetails<int>>[];
2110
    final List<double> acceptedDoubles = <double>[];
2111
    final List<DragTargetDetails<double>> acceptedDoublesDetails = <DragTargetDetails<double>>[];
2112

2113 2114
    await tester.pumpWidget(MaterialApp(
      home: Column(
2115
        children: <Widget>[
2116
          const Draggable<int>(
2117
            data: 1,
2118
            feedback: Text('IntDragging'),
2119
            child: Text('IntSource'),
2120
          ),
2121
          const Draggable<double>(
2122
            data: 1.0,
2123
            feedback: Text('DoubleDragging'),
2124
            child: Text('DoubleSource'),
2125
          ),
2126
          Stack(
2127
            children: <Widget>[
2128
              DragTarget<int>(
2129
                builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2130 2131
                  return const IgnorePointer(
                    child: SizedBox(
2132
                      height: 100.0,
2133
                      child: Text('Target1'),
2134
                    ),
2135 2136
                  );
                },
2137
                onAccept: acceptedInts.add,
2138
                onAcceptWithDetails: acceptedIntsDetails.add,
2139
              ),
2140
              DragTarget<double>(
2141
                builder: (BuildContext context, List<double?> data, List<dynamic> rejects) {
2142 2143
                  return const IgnorePointer(
                    child: SizedBox(
2144
                      height: 100.0,
2145
                      child: Text('Target2'),
2146
                    ),
2147 2148
                  );
                },
2149
                onAccept: acceptedDoubles.add,
2150
                onAcceptWithDetails: acceptedDoublesDetails.add,
2151
              ),
2152 2153 2154 2155
            ],
          ),
        ],
      ),
2156 2157 2158
    ));

    expect(acceptedInts, isEmpty);
2159
    expect(acceptedIntsDetails, isEmpty);
2160
    expect(acceptedDoubles, isEmpty);
2161
    expect(acceptedDoublesDetails, isEmpty);
2162 2163 2164 2165 2166 2167 2168
    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);

2169 2170 2171
    final Offset intLocation = tester.getCenter(find.text('IntSource'));
    final Offset doubleLocation = tester.getCenter(find.text('DoubleSource'));
    final Offset targetLocation = tester.getCenter(find.text('Target1'));
2172 2173

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

    expect(acceptedInts, isEmpty);
2178
    expect(acceptedIntsDetails, isEmpty);
2179
    expect(acceptedDoubles, isEmpty);
2180
    expect(acceptedDoublesDetails, isEmpty);
2181 2182 2183
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsOneWidget);

2184 2185
    await doubleGesture.moveTo(targetLocation);
    await tester.pump();
2186 2187

    expect(acceptedInts, isEmpty);
2188
    expect(acceptedIntsDetails, isEmpty);
2189
    expect(acceptedDoubles, isEmpty);
2190
    expect(acceptedDoublesDetails, isEmpty);
2191 2192 2193
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsOneWidget);

2194 2195
    await doubleGesture.up();
    await tester.pump();
2196 2197

    expect(acceptedInts, isEmpty);
2198
    expect(acceptedIntsDetails, isEmpty);
2199
    expect(acceptedDoubles, equals(<double>[1.0]));
2200 2201
    expect(acceptedDoublesDetails, hasLength(1));
    expect(acceptedDoublesDetails.first.offset, const Offset(112.0, 122.0));
2202 2203 2204 2205
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsNothing);

    acceptedDoubles.clear();
2206
    acceptedDoublesDetails.clear();
2207 2208

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

    expect(acceptedInts, isEmpty);
2213
    expect(acceptedIntsDetails, isEmpty);
2214
    expect(acceptedDoubles, isEmpty);
2215
    expect(acceptedDoublesDetails, isEmpty);
2216 2217 2218
    expect(find.text('IntDragging'), findsOneWidget);
    expect(find.text('DoubleDragging'), findsNothing);

2219 2220
    await intGesture.moveTo(targetLocation);
    await tester.pump();
2221 2222

    expect(acceptedInts, isEmpty);
2223
    expect(acceptedIntsDetails, isEmpty);
2224
    expect(acceptedDoubles, isEmpty);
2225
    expect(acceptedDoublesDetails, isEmpty);
2226 2227 2228
    expect(find.text('IntDragging'), findsOneWidget);
    expect(find.text('DoubleDragging'), findsNothing);

2229 2230
    await intGesture.up();
    await tester.pump();
2231 2232

    expect(acceptedInts, equals(<int>[1]));
2233 2234
    expect(acceptedIntsDetails, hasLength(1));
    expect(acceptedIntsDetails.first.offset, const Offset(184.0, 122.0));
2235
    expect(acceptedDoubles, isEmpty);
2236
    expect(acceptedDoublesDetails, isEmpty);
2237 2238 2239
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsNothing);
  });
2240

2241
  testWidgets('Drag and drop - allow pass through of unaccepted data twice test', (WidgetTester tester) async {
2242
    final List<DragTargetData> acceptedDragTargetDatas = <DragTargetData>[];
2243
    final List<DragTargetDetails<DragTargetData>> acceptedDragTargetDataDetails = <DragTargetDetails<DragTargetData>>[];
2244
    final List<ExtendedDragTargetData> acceptedExtendedDragTargetDatas = <ExtendedDragTargetData>[];
2245
    final List<DragTargetDetails<ExtendedDragTargetData>> acceptedExtendedDragTargetDataDetails = <DragTargetDetails<ExtendedDragTargetData>>[];
2246 2247 2248
    final DragTargetData dragTargetData = DragTargetData();
    await tester.pumpWidget(MaterialApp(
      home: Column(
2249
        children: <Widget>[
2250
          Draggable<DragTargetData>(
2251
            data: dragTargetData,
2252
            feedback: const Text('Dragging'),
2253
            child: const Text('Source'),
2254
          ),
2255
          Stack(
2256
            children: <Widget>[
2257
              DragTarget<DragTargetData>(
2258
                builder: (BuildContext context, List<DragTargetData?> data, List<dynamic> rejects) {
2259 2260
                  return const IgnorePointer(
                    child: SizedBox(
2261
                      height: 100.0,
2262
                      child: Text('Target1'),
2263
                    ),
2264
                  );
2265
                }, onAccept: acceptedDragTargetDatas.add,
2266
                onAcceptWithDetails: acceptedDragTargetDataDetails.add,
2267
              ),
2268
              DragTarget<ExtendedDragTargetData>(
2269
                builder: (BuildContext context, List<ExtendedDragTargetData?> data, List<dynamic> rejects) {
2270 2271
                  return const IgnorePointer(
                    child: SizedBox(
2272
                      height: 100.0,
2273
                      child: Text('Target2'),
2274
                    ),
2275 2276
                  );
                },
2277
                onAccept: acceptedExtendedDragTargetDatas.add,
2278
                onAcceptWithDetails: acceptedExtendedDragTargetDataDetails.add,
2279
              ),
2280 2281 2282 2283
            ],
          ),
        ],
      ),
2284
    ));
2285

2286 2287
    final Offset dragTargetLocation = tester.getCenter(find.text('Source'));
    final Offset targetLocation = tester.getCenter(find.text('Target1'));
2288

2289
    for (int i = 0; i < 2; i += 1) {
2290
      final TestGesture gesture = await tester.startGesture(dragTargetLocation);
2291 2292 2293 2294 2295
      await tester.pump();
      await gesture.moveTo(targetLocation);
      await tester.pump();
      await gesture.up();
      await tester.pump();
2296

2297
      expect(acceptedDragTargetDatas, equals(<DragTargetData>[dragTargetData]));
2298 2299
      expect(acceptedDragTargetDataDetails, hasLength(1));
      expect(acceptedDragTargetDataDetails.first.offset, const Offset(256.0, 74.0));
2300
      expect(acceptedExtendedDragTargetDatas, isEmpty);
2301
      expect(acceptedExtendedDragTargetDataDetails, isEmpty);
2302

2303
      acceptedDragTargetDatas.clear();
2304
      acceptedDragTargetDataDetails.clear();
2305
      await tester.pump();
2306
    }
2307
  });
2308

2309
  testWidgets('Drag and drop - maxSimultaneousDrags', (WidgetTester tester) async {
2310
    final List<int> accepted = <int>[];
2311
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
2312 2313

    Widget build(int maxSimultaneousDrags) {
2314 2315
      return MaterialApp(
        home: Column(
2316
          children: <Widget>[
2317
            Draggable<int>(
2318 2319
              data: 1,
              maxSimultaneousDrags: maxSimultaneousDrags,
2320
              feedback: const Text('Dragging'),
2321
              child: const Text('Source'),
2322
            ),
2323
            DragTarget<int>(
2324
              builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2325
                return const SizedBox(height: 100.0, child: Text('Target'));
2326
              },
2327
              onAccept: accepted.add,
2328
              onAcceptWithDetails: acceptedDetails.add,
2329
            ),
2330 2331
          ],
        ),
2332 2333 2334 2335 2336
      );
    }

    await tester.pumpWidget(build(0));

2337 2338
    final Offset firstLocation = tester.getCenter(find.text('Source'));
    final Offset secondLocation = tester.getCenter(find.text('Target'));
2339 2340

    expect(accepted, isEmpty);
2341
    expect(acceptedDetails, isEmpty);
2342 2343 2344 2345
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, isEmpty);
2350
    expect(acceptedDetails, isEmpty);
2351 2352 2353 2354 2355 2356 2357 2358 2359
    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);
2360
    expect(acceptedDetails, isEmpty);
2361 2362 2363 2364
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, isEmpty);
2369
    expect(acceptedDetails, isEmpty);
2370 2371 2372 2373
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, isEmpty);
2378
    expect(acceptedDetails, isEmpty);
2379 2380 2381 2382
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNWidgets(2));
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, isEmpty);
2387
    expect(acceptedDetails, isEmpty);
2388 2389 2390 2391 2392 2393 2394 2395 2396 2397
    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);
2398
    expect(acceptedDetails, isEmpty);
2399 2400 2401 2402 2403 2404 2405 2406
    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]));
2407 2408
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
2409 2410 2411 2412 2413 2414 2415 2416
    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]));
2417 2418 2419
    expect(acceptedDetails, hasLength(2));
    expect(acceptedDetails[0].offset, const Offset(256.0, 74.0));
    expect(acceptedDetails[1].offset, const Offset(256.0, 74.0));
2420 2421 2422 2423 2424 2425 2426 2427
    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]));
2428 2429 2430
    expect(acceptedDetails, hasLength(2));
    expect(acceptedDetails[0].offset, const Offset(256.0, 74.0));
    expect(acceptedDetails[1].offset, const Offset(256.0, 74.0));
2431 2432 2433 2434 2435
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
  });

2436
  testWidgets('Drag and drop - onAccept is not called if dropped with null data', (WidgetTester tester) async {
2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497
    bool onAcceptCalled = false;
    bool onAcceptWithDetailsCalled = false;

    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<int>(
            feedback: Text('Dragging'),
            child: Text('Source'),
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
              return const SizedBox(height: 100.0, child: Text('Target'));
            },
            onAccept: (int data) {
              onAcceptCalled = true;
            },
            onAcceptWithDetails: (DragTargetDetails<int> details) {
              onAcceptWithDetailsCalled =true;
            },
          ),
        ],
      ),
    ));

    expect(onAcceptCalled, isFalse);
    expect(onAcceptWithDetailsCalled, isFalse);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

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

    expect(onAcceptCalled, isFalse);
    expect(onAcceptWithDetailsCalled, isFalse);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

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

    expect(onAcceptCalled, isFalse);
    expect(onAcceptWithDetailsCalled, isFalse);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

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

    expect(onAcceptCalled, isFalse, reason: 'onAccept should not be called when data is null');
    expect(onAcceptWithDetailsCalled, isFalse, reason: 'onAcceptWithDetails should not be called when data is null');
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
  });

2498
  testWidgets('Draggable disposes recognizer', (WidgetTester tester) async {
2499 2500 2501
    late final OverlayEntry entry;
    addTearDown(() => entry..remove()..dispose());

2502
    bool didTap = false;
2503
    await tester.pumpWidget(
2504
      Directionality(
2505
        textDirection: TextDirection.ltr,
2506
        child: Overlay(
2507
          initialEntries: <OverlayEntry>[
2508
            entry = OverlayEntry(
2509
              builder: (BuildContext context) => GestureDetector(
2510 2511 2512
                onTap: () {
                  didTap = true;
                },
2513
                child: Draggable<Object>(
2514
                  feedback: Container(
2515 2516 2517 2518
                    width: 100.0,
                    height: 100.0,
                    color: const Color(0xFFFF0000),
                  ),
2519 2520 2521
                  child: Container(
                    color: const Color(0xFFFFFF00),
                  ),
2522
                ),
2523
              ),
2524 2525 2526 2527 2528
            ),
          ],
        ),
      ),
    );
2529

2530
    final TestGesture gesture = await tester.startGesture(const Offset(10.0, 10.0));
2531 2532 2533 2534
    expect(didTap, isFalse);

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

    // Finish gesture to release resources.
    await gesture.up();
    await tester.pumpAndSettle();
2541 2542
  });

2543
  // Regression test for https://github.com/flutter/flutter/issues/6128.
2544
  testWidgets('Draggable plays nice with onTap', (WidgetTester tester) async {
2545 2546 2547
    late final OverlayEntry entry;
    addTearDown(() => entry..remove()..dispose());

2548
    await tester.pumpWidget(
2549
      Directionality(
2550
        textDirection: TextDirection.ltr,
2551
        child: Overlay(
2552
          initialEntries: <OverlayEntry>[
2553
            entry = OverlayEntry(
2554
              builder: (BuildContext context) => GestureDetector(
2555
                onTap: () { /* registers a tap recognizer */ },
2556
                child: Draggable<Object>(
2557
                  feedback: Container(
2558 2559 2560 2561
                    width: 100.0,
                    height: 100.0,
                    color: const Color(0xFFFF0000),
                  ),
2562 2563 2564
                  child: Container(
                    color: const Color(0xFFFFFF00),
                  ),
2565
                ),
2566
              ),
2567 2568 2569 2570 2571
            ),
          ],
        ),
      ),
    );
2572

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

2576
    await firstGesture.moveBy(const Offset(100.0, 0.0));
2577 2578
    await secondGesture.up();
  });
2579

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

2584 2585
    await tester.pumpWidget(MaterialApp(
      home: Column(
2586
        children: <Widget>[
2587
          const Draggable<int>(
2588
            data: 1,
2589
            feedback: Text('Dragging'),
2590
            child: Text('Source'),
2591
          ),
2592
          DragTarget<int>(
2593
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2594
              return const Text('Target');
2595
            },
2596
            onAccept: (int? data) {
2597
              events.add('drop');
2598
            },
2599 2600 2601
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
2602
          ),
2603 2604
        ],
      ),
2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615
    ));

    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'));
2616
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
2617 2618 2619 2620 2621 2622 2623 2624
    await tester.pump();

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

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

2625
    await tester.pumpWidget(const MaterialApp(
2626
      home: Column(
2627
        children: <Widget>[
2628
          Draggable<int>(
2629
            data: 1,
2630
            feedback: Text('Dragging'),
2631
            child: Text('Source'),
2632
          ),
2633 2634
        ],
      ),
2635 2636 2637 2638 2639 2640
    ));

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

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

2646 2647
    await tester.pumpWidget(MaterialApp(
      home: Column(
2648 2649 2650
        children: <Widget>[
          const Draggable<int>(
            data: 1,
2651
            feedback: Text('Dragging'),
2652
            child: Text('Source'),
2653
          ),
2654
          DragTarget<int>(
2655
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2656
              return const SizedBox(height: 100.0, child: Text('Target'));
2657
            },
2658
            onAccept: accepted.add,
2659
            onAcceptWithDetails: acceptedDetails.add,
2660 2661 2662
          ),
        ],
      ),
2663 2664 2665
    ));

    expect(accepted, isEmpty);
2666
    expect(acceptedDetails, isEmpty);
2667 2668 2669 2670
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

2671
    final Offset firstLocation = tester.getCenter(find.text('Source'));
2672
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
2673 2674 2675
    await tester.pump();

    expect(accepted, isEmpty);
2676
    expect(acceptedDetails, isEmpty);
2677 2678 2679 2680
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

2681 2682
    await tester.pumpWidget(MaterialApp(
      home: Column(
2683
        children: <Widget>[
2684
          DragTarget<int>(
2685
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2686
              return const SizedBox(height: 100.0, child: Text('Target'));
2687
            },
2688
            onAccept: accepted.add,
2689
            onAcceptWithDetails: acceptedDetails.add,
2690 2691 2692
          ),
        ],
      ),
2693 2694 2695
    ));

    expect(accepted, isEmpty);
2696
    expect(acceptedDetails, isEmpty);
2697 2698 2699 2700
    expect(find.text('Source'), findsNothing);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

2701
    final Offset secondLocation = tester.getCenter(find.text('Target'));
2702 2703 2704 2705
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(accepted, isEmpty);
2706
    expect(acceptedDetails, isEmpty);
2707 2708 2709 2710 2711 2712 2713 2714
    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]));
2715 2716
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 26.0));
2717 2718 2719 2720 2721
    expect(find.text('Source'), findsNothing);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
  });

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

2725 2726 2727 2728
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: Center(
          child: GestureDetector(
2729 2730 2731
            onTap: () {
              events.add('tap');
            },
2732
            child: const LongPressDraggable<int>(
2733 2734
              feedback: Text('Feedback'),
              child: Text('X'),
2735 2736 2737 2738 2739 2740 2741 2742 2743 2744
            ),
          ),
        ),
      ),
    ));

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

2746
  testWidgets('long-press draggable calls onDragEnd called if dropped on accepting target', (WidgetTester tester) async {
2747
    final List<int> accepted = <int>[];
2748
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
2749
    bool onDragEndCalled = false;
2750
    late DraggableDetails onDragEndDraggableDetails;
2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761

    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          LongPressDraggable<int>(
            data: 1,
            feedback: const Text('Dragging'),
            onDragEnd: (DraggableDetails details) {
              onDragEndCalled = true;
              onDragEndDraggableDetails = details;
            },
2762
            child: const Text('Source'),
2763 2764
          ),
          DragTarget<int>(
2765
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2766
              return const SizedBox(height: 100.0, child: Text('Target'));
2767
            },
2768
            onAccept: accepted.add,
2769
            onAcceptWithDetails: acceptedDetails.add,
2770 2771 2772 2773 2774 2775
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);
2776
    expect(acceptedDetails, isEmpty);
2777 2778 2779 2780 2781 2782 2783 2784 2785 2786
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isFalse);

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

    expect(accepted, isEmpty);
2787
    expect(acceptedDetails, isEmpty);
2788 2789 2790 2791 2792 2793 2794 2795
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isFalse);

    await tester.pump(kLongPressTimeout);

    expect(accepted, isEmpty);
2796
    expect(acceptedDetails, isEmpty);
2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isFalse);


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

    expect(accepted, isEmpty);
2808
    expect(acceptedDetails, isEmpty);
2809 2810 2811 2812 2813 2814 2815 2816 2817
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isFalse);

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

    final Offset droppedLocation = tester.getTopLeft(find.text('Target'));
2818 2819
    final Offset expectedDropOffset = Offset(droppedLocation.dx, secondLocation.dy - firstLocation.dy);

2820
    expect(accepted, equals(<int>[1]));
2821 2822
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, expectedDropOffset);
2823 2824 2825 2826 2827 2828 2829
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragEndCalled, isTrue);
    expect(onDragEndDraggableDetails, isNotNull);
    expect(onDragEndDraggableDetails.wasAccepted, isTrue);
    expect(onDragEndDraggableDetails.velocity, equals(Velocity.zero));
2830
    expect(onDragEndDraggableDetails.offset, equals(expectedDropOffset));
2831 2832
  });

2833
  testWidgets('long-press draggable calls onDragCompleted called if dropped on accepting target', (WidgetTester tester) async {
2834
    final List<int> accepted = <int>[];
2835
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
2836 2837
    bool onDragCompletedCalled = false;

2838 2839
    await tester.pumpWidget(MaterialApp(
      home: Column(
2840
        children: <Widget>[
2841
          LongPressDraggable<int>(
2842 2843 2844 2845 2846
            data: 1,
            feedback: const Text('Dragging'),
            onDragCompleted: () {
              onDragCompletedCalled = true;
            },
2847
            child: const Text('Source'),
2848
          ),
2849
          DragTarget<int>(
2850
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2851
              return const SizedBox(height: 100.0, child: Text('Target'));
2852
            },
2853
            onAccept: accepted.add,
2854
            onAcceptWithDetails: acceptedDetails.add,
2855 2856 2857 2858 2859 2860
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);
2861
    expect(acceptedDetails, isEmpty);
2862 2863 2864 2865 2866 2867 2868 2869 2870 2871
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);

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

    expect(accepted, isEmpty);
2872
    expect(acceptedDetails, isEmpty);
2873 2874 2875 2876 2877 2878 2879 2880
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);

    await tester.pump(kLongPressTimeout);

    expect(accepted, isEmpty);
2881
    expect(acceptedDetails, isEmpty);
2882 2883 2884 2885 2886 2887 2888 2889 2890 2891
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);

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

    expect(accepted, isEmpty);
2892
    expect(acceptedDetails, isEmpty);
2893 2894 2895 2896 2897 2898 2899 2900 2901
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);

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

    expect(accepted, equals(<int>[1]));
2902
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
2903 2904 2905 2906 2907 2908
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isTrue);
  });

2909
  testWidgets('long-press draggable calls onDragStartedCalled after long press', (WidgetTester tester) async {
2910 2911
    bool onDragStartedCalled = false;

2912 2913
    await tester.pumpWidget(MaterialApp(
      home: LongPressDraggable<int>(
2914 2915 2916 2917 2918
        data: 1,
        feedback: const Text('Dragging'),
        onDragStarted: () {
          onDragStartedCalled = true;
        },
2919
        child: const Text('Source'),
2920 2921 2922 2923 2924 2925 2926 2927
      ),
    ));

    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(onDragStartedCalled, isFalse);

    final Offset firstLocation = tester.getCenter(find.text('Source'));
2928
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939
    await tester.pump();

    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(onDragStartedCalled, isFalse);

    await tester.pump(kLongPressTimeout);

    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(onDragStartedCalled, isTrue);
2940 2941 2942 2943

    // Finish gesture to release resources.
    await gesture.up();
    await tester.pumpAndSettle();
2944
  });
2945

2946
  testWidgets('Custom long press delay for LongPressDraggable', (WidgetTester tester) async {
2947 2948 2949 2950 2951 2952 2953 2954 2955
    bool onDragStartedCalled = false;
    await tester.pumpWidget(MaterialApp(
      home: LongPressDraggable<int>(
        data: 1,
        delay: const Duration(seconds: 2),
        feedback: const Text('Dragging'),
        onDragStarted: () {
          onDragStartedCalled = true;
        },
2956
        child: const Text('Source'),
2957 2958 2959 2960 2961 2962
      ),
    ));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(onDragStartedCalled, isFalse);
    final Offset firstLocation = tester.getCenter(find.text('Source'));
2963
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977
    await tester.pump();
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(onDragStartedCalled, isFalse);
    // Halfway into the long press duration.
    await tester.pump(const Duration(seconds: 1));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(onDragStartedCalled, isFalse);
    // Long press draggable should be showing.
    await tester.pump(const Duration(seconds: 1));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(onDragStartedCalled, isTrue);
2978 2979 2980 2981

    // Finish gesture to release resources.
    await gesture.up();
    await tester.pumpAndSettle();
2982 2983
  });

2984
  testWidgets('Default long press delay for LongPressDraggable', (WidgetTester tester) async {
2985 2986 2987 2988 2989 2990 2991 2992
    bool onDragStartedCalled = false;
    await tester.pumpWidget(MaterialApp(
      home: LongPressDraggable<int>(
        data: 1,
        feedback: const Text('Dragging'),
        onDragStarted: () {
          onDragStartedCalled = true;
        },
2993
        child: const Text('Source'),
2994 2995 2996 2997 2998 2999
      ),
    ));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(onDragStartedCalled, isFalse);
    final Offset firstLocation = tester.getCenter(find.text('Source'));
3000
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014
    await tester.pump();
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(onDragStartedCalled, isFalse);
    // Halfway into the long press duration.
    await tester.pump(const Duration(milliseconds: 250));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(onDragStartedCalled, isFalse);
    // Long press draggable should be showing.
    await tester.pump(const Duration(milliseconds: 250));
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(onDragStartedCalled, isTrue);
3015 3016 3017 3018

    // Finish gesture to release resources.
    await gesture.up();
    await tester.pumpAndSettle();
3019
  });
3020

3021
  testWidgets('long-press draggable calls Haptic Feedback onStart', (WidgetTester tester) async {
3022 3023 3024
    await _testLongPressDraggableHapticFeedback(tester: tester, hapticFeedbackOnStart: true, expectedHapticFeedbackCount: 1);
  });

3025
  testWidgets('long-press draggable can disable Haptic Feedback', (WidgetTester tester) async {
3026 3027 3028
    await _testLongPressDraggableHapticFeedback(tester: tester, hapticFeedbackOnStart: false, expectedHapticFeedbackCount: 0);
  });

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

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

3037
  testWidgets('Drag feedback is put on root overlay with [rootOverlay] flag', (WidgetTester tester) async {
3038 3039 3040 3041 3042 3043 3044 3045
      final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
      final GlobalKey<NavigatorState> childNavigatorKey = GlobalKey<NavigatorState>();
      // Create a [MaterialApp], with a nested [Navigator], which has the
      // [Draggable].
      await tester.pumpWidget(MaterialApp(
        navigatorKey: rootNavigatorKey,
        home: Column(
          children: <Widget>[
3046
            SizedBox(
3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057
              height: 200.0,
              child: Navigator(
                key: childNavigatorKey,
                onGenerateRoute: (RouteSettings settings) {
                  if (settings.name == '/') {
                    return MaterialPageRoute<void>(
                      settings: settings,
                      builder: (BuildContext context) => const Draggable<int>(
                        data: 1,
                        feedback: Text('Dragging'),
                        rootOverlay: true,
3058
                        child: Text('Source'),
3059 3060 3061 3062 3063 3064 3065 3066
                      ),
                    );
                  }
                  throw UnsupportedError('Unsupported route: $settings');
                },
              ),
            ),
            DragTarget<int>(
3067
              builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
3068 3069
                return const SizedBox(
                    height: 300.0, child: Center(child: Text('Target 1')),
3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088
                );
              },
            ),
          ],
        ),
      ));

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

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

      // Expect that the feedback widget is a descendant of the root overlay,
      // but not a descendant of the child overlay.
      expect(
3089 3090 3091 3092 3093 3094
        find.descendant(
          of: find.byType(Overlay).first,
          matching: find.text('Dragging'),
        ),
        findsOneWidget,
      );
3095
      expect(
3096 3097 3098 3099 3100 3101
        find.descendant(
          of: find.byType(Overlay).last,
          matching: find.text('Dragging'),
        ),
        findsNothing,
      );
3102
    });
3103

3104
  // Regression test for https://github.com/flutter/flutter/issues/72483
3105
  testWidgets('Drag and drop - DragTarget<Object> can accept Draggable<int> data', (WidgetTester tester) async {
3106 3107 3108 3109 3110 3111 3112
    final List<Object> accepted = <Object>[];
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<int>(
            data: 1,
            feedback: Text('Dragging'),
3113
            child: Text('Source'),
3114 3115 3116
          ),
          DragTarget<Object>(
            builder: (BuildContext context, List<Object?> data, List<dynamic> rejects) {
3117
              return const SizedBox(height: 100.0, child: Text('Target'));
3118
            },
3119
            onAccept: accepted.add,
3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);

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

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

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

    expect(accepted, equals(<int>[1]));
  });

3141
  testWidgets('Drag and drop - DragTarget<int> can accept Draggable<Object> data when runtime type is int', (WidgetTester tester) async {
3142 3143 3144 3145 3146 3147 3148
    final List<int> accepted = <int>[];
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<Object>(
            data: 1,
            feedback: Text('Dragging'),
3149
            child: Text('Source'),
3150 3151 3152
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
3153
              return const SizedBox(height: 100.0, child: Text('Target'));
3154
            },
3155
            onAccept: accepted.add,
3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);

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

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

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

    expect(accepted, equals(<int>[1]));
  });

3177
  testWidgets('Drag and drop - DragTarget<int> should not accept Draggable<Object> data when runtime type null', (WidgetTester tester) async {
3178 3179 3180 3181 3182 3183 3184
    final List<int> accepted = <int>[];
    bool isReceiveNullDataForCheck = false;
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<Object>(
            feedback: Text('Dragging'),
3185
            child: Text('Source'),
3186 3187 3188
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
3189
              return const SizedBox(height: 100.0, child: Text('Target'));
3190
            },
3191
            onAccept: accepted.add,
3192
            onWillAccept: (int? data) {
3193
              if (data == null) {
3194
                isReceiveNullDataForCheck = true;
3195
              }
3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219
              return data != null;
            },
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);

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

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

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

    expect(accepted, isEmpty);
    expect(isReceiveNullDataForCheck, true);
  });

3220
  testWidgets('Drag and drop can contribute semantics', (WidgetTester tester) async {
3221 3222 3223
    final SemanticsTester semantics = SemanticsTester(tester);
    await tester.pumpWidget(MaterialApp(
        home: ListView(
3224
          scrollDirection: Axis.horizontal,
3225
          addSemanticIndexes: false,
3226
          children: <Widget>[
3227
            DragTarget<int>(
3228
              builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
3229 3230 3231
                return const Text('Target');
              },
            ),
3232
            Container(width: 400.0),
3233 3234
            const Draggable<int>(
              data: 1,
3235 3236
              feedback: Text('H'),
              childWhenDragging: SizedBox(),
3237 3238
              axis: Axis.horizontal,
              ignoringFeedbackSemantics: false,
3239
              child: Text('H'),
3240 3241 3242
            ),
            const Draggable<int>(
              data: 2,
3243 3244
              feedback: Text('V'),
              childWhenDragging: SizedBox(),
3245 3246
              axis: Axis.vertical,
              ignoringFeedbackSemantics: false,
3247
              child: Text('V'),
3248 3249 3250
            ),
            const Draggable<int>(
              data: 3,
3251 3252
              feedback: Text('N'),
              childWhenDragging: SizedBox(),
3253
              child: Text('N'),
3254 3255 3256 3257
            ),
          ],
        ),
    ));
3258

3259
    expect(semantics, hasSemantics(
3260
      TestSemantics.root(
3261
        children: <TestSemantics>[
3262
          TestSemantics(
3263 3264 3265
            id: 1,
            textDirection: TextDirection.ltr,
            children: <TestSemantics>[
3266
              TestSemantics(
3267 3268
                id: 2,
                children: <TestSemantics>[
3269
                  TestSemantics(
3270
                    id: 3,
3271
                    flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
3272
                    children: <TestSemantics>[
3273
                      TestSemantics(
3274
                        id: 4,
3275
                        children: <TestSemantics>[
3276
                          TestSemantics(
3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305
                            id: 9,
                            flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
                            actions: <SemanticsAction>[SemanticsAction.scrollLeft],
                            children: <TestSemantics>[
                              TestSemantics(
                                id: 5,
                                tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
                                label: 'Target',
                                textDirection: TextDirection.ltr,
                              ),
                              TestSemantics(
                                id: 6,
                                tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
                                label: 'H',
                                textDirection: TextDirection.ltr,
                              ),
                              TestSemantics(
                                id: 7,
                                tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
                                label: 'V',
                                textDirection: TextDirection.ltr,
                              ),
                              TestSemantics(
                                id: 8,
                                tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
                                label: 'N',
                                textDirection: TextDirection.ltr,
                              ),
                            ],
3306 3307 3308 3309 3310 3311 3312 3313 3314 3315
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
3316 3317 3318 3319
      ),
      ignoreTransform: true,
      ignoreRect: true,
    ));
3320 3321 3322 3323 3324 3325 3326

    final Offset firstLocation = tester.getTopLeft(find.text('N'));
    final Offset secondLocation = firstLocation + const Offset(300.0, 300.0);
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
3327

3328
    expect(semantics, hasSemantics(
3329
      TestSemantics.root(
3330
        children: <TestSemantics>[
3331
          TestSemantics(
3332 3333 3334
            id: 1,
            textDirection: TextDirection.ltr,
            children: <TestSemantics>[
3335
              TestSemantics(
3336 3337
                id: 2,
                children: <TestSemantics>[
3338
                  TestSemantics(
3339
                    id: 3,
3340
                    flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
3341
                    children: <TestSemantics>[
3342
                      TestSemantics(
3343
                        id: 4,
3344
                        children: <TestSemantics>[
3345
                          TestSemantics(
3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368
                            id: 9,
                            flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
                            children: <TestSemantics>[
                              TestSemantics(
                                id: 5,
                                tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
                                label: 'Target',
                                textDirection: TextDirection.ltr,
                              ),
                              TestSemantics(
                                id: 6,
                                tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
                                label: 'H',
                                textDirection: TextDirection.ltr,
                              ),
                              TestSemantics(
                                id: 7,
                                tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
                                label: 'V',
                                textDirection: TextDirection.ltr,
                              ),
                              /// N is moved offscreen.
                            ],
3369 3370 3371 3372 3373 3374 3375 3376 3377 3378
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
3379 3380 3381 3382
      ),
      ignoreTransform: true,
      ignoreRect: true,
    ));
3383
    semantics.dispose();
3384
  });
3385

3386
  testWidgets('Drag and drop - when a dragAnchorStrategy is provided it gets called', (WidgetTester tester) async {
3387 3388 3389 3390 3391 3392 3393 3394 3395
    bool dragAnchorStrategyCalled = false;

    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
            feedback: const Text('Feedback'),
            dragAnchorStrategy: (Draggable<Object> widget, BuildContext context, Offset position) {
              dragAnchorStrategyCalled = true;
3396
              return Offset.zero;
3397
            },
3398
            child: const Text('Source'),
3399
          ),
3400 3401 3402 3403 3404
        ],
      ),
    ));

    final Offset location = tester.getCenter(find.text('Source'));
3405
    final TestGesture gesture = await tester.startGesture(location, pointer: 7);
3406 3407

    expect(dragAnchorStrategyCalled, true);
3408 3409 3410 3411

    // Finish gesture to release resources.
    await gesture.up();
    await tester.pumpAndSettle();
3412 3413
  });

3414
  testWidgets('configurable Draggable hit test behavior', (WidgetTester tester) async {
3415 3416 3417
    const HitTestBehavior hitTestBehavior = HitTestBehavior.deferToChild;

    await tester.pumpWidget(
3418
      const MaterialApp(
3419
        home: Column(
3420
          children: <Widget>[
3421
            Draggable<int>(
3422 3423
              feedback: SizedBox(height: 50.0, child: Text('Draggable')),
              child: SizedBox(height: 50.0, child: Text('Target')),
3424 3425 3426 3427 3428 3429 3430 3431 3432
            ),
          ],
        ),
      ),
    );

    expect(tester.widget<Listener>(find.byType(Listener).first).behavior, hitTestBehavior);
  });

3433
  // Regression test for https://github.com/flutter/flutter/issues/92083
3434 3435 3436 3437
  testWidgets('feedback respect the MouseRegion cursor configure',
  // TODO(polina-c): clean up leaks, https://github.com/flutter/flutter/issues/134787
  experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(),
  (WidgetTester tester) async {
3438
    await tester.pumpWidget(
3439
      const MaterialApp(
3440
        home: Column(
3441
          children: <Widget>[
3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464
            Draggable<int>(
              ignoringFeedbackPointer: false,
              feedback: MouseRegion(
                cursor: SystemMouseCursors.grabbing,
                child: SizedBox(height: 50.0, child: Text('Draggable')),
              ),
              child: SizedBox(height: 50.0, child: Text('Target')),
            ),
          ],
        ),
      ),
    );

    final Offset location = tester.getCenter(find.text('Target'));
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    await gesture.addPointer(location: location);

    await gesture.down(location);
    await tester.pump();

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

3465
  testWidgets('configurable feedback ignore pointer behavior', (WidgetTester tester) async {
3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493
    bool onTap = false;
    await tester.pumpWidget(
      MaterialApp(
        home: Column(
          children: <Widget>[
            Draggable<int>(
              ignoringFeedbackPointer: false,
              feedback: GestureDetector(
                onTap: () => onTap = true,
                child: const SizedBox(height: 50.0, child: Text('Draggable')),
              ),
              child: const SizedBox(height: 50.0, child: Text('Target')),
            ),
          ],
        ),
      ),
    );

    final Offset location = tester.getCenter(find.text('Target'));
    final TestGesture gesture = await tester.startGesture(location, pointer: 7);
    final Offset secondLocation = location + const Offset(7.0, 7.0);
    await gesture.moveTo(secondLocation);
    await tester.pump();

    await tester.tap(find.text('Draggable'));
    expect(onTap, true);
  });

3494
  testWidgets('configurable feedback ignore pointer behavior - LongPressDraggable', (WidgetTester tester) async {
3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524
    bool onTap = false;
    await tester.pumpWidget(
      MaterialApp(
        home: Column(
          children: <Widget>[
            LongPressDraggable<int>(
              ignoringFeedbackPointer: false,
              feedback: GestureDetector(
                onTap: () => onTap = true,
                child: const SizedBox(height: 50.0, child: Text('Draggable')),
              ),
              child: const SizedBox(height: 50.0, child: Text('Target')),
            ),
          ],
        ),
      ),
    );

    final Offset location = tester.getCenter(find.text('Target'));
    final TestGesture gesture = await tester.startGesture(location, pointer: 7);
    await tester.pump(kLongPressTimeout);

    final Offset secondLocation = location + const Offset(7.0, 7.0);
    await gesture.moveTo(secondLocation);
    await tester.pump();

    await tester.tap(find.text('Draggable'));
    expect(onTap, true);
  });

3525
  testWidgets('configurable DragTarget hit test behavior', (WidgetTester tester) async {
3526 3527 3528 3529 3530 3531 3532 3533
    const HitTestBehavior hitTestBehavior = HitTestBehavior.deferToChild;

    await tester.pumpWidget(
      MaterialApp(
        home: Column(
          children: <Widget>[
            DragTarget<int>(
              hitTestBehavior: hitTestBehavior,
3534
              builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
3535
                return const SizedBox(height: 100.0, child: Text('Target'));
3536 3537 3538 3539 3540 3541 3542 3543 3544
              },
            ),
          ],
        ),
      ),
    );

    expect(tester.widget<MetaData>(find.byType(MetaData)).behavior, hitTestBehavior);
  });
3545

3546
  testWidgets('LongPressDraggable.dragAnchorStrategy', (WidgetTester tester) async {
3547 3548 3549
    const Widget widget1 = Placeholder(key: ValueKey<int>(1));
    const Widget widget2 = Placeholder(key: ValueKey<int>(2));
    Offset dummyStrategy(Draggable<Object> draggable, BuildContext context, Offset position) => Offset.zero;
3550 3551 3552 3553
    expect(const LongPressDraggable<int>(feedback: widget2, child: widget1), isA<Draggable<int>>());
    expect(const LongPressDraggable<int>(feedback: widget2, child: widget1).child, widget1);
    expect(const LongPressDraggable<int>(feedback: widget2, child: widget1).feedback, widget2);
    expect(LongPressDraggable<int>(feedback: widget2, dragAnchorStrategy: dummyStrategy, child: widget1).dragAnchorStrategy, dummyStrategy);
3554
  });
3555

3556
  testWidgets('Test allowedButtonsFilter', (WidgetTester tester) async {
3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596
    Widget build(bool Function(int buttons)? allowedButtonsFilter) {
      return MaterialApp(
        home: Draggable<int>(
          key: UniqueKey(),
          allowedButtonsFilter: allowedButtonsFilter,
          feedback: const Text('Dragging'),
          child: const Text('Source'),
        ),
      );
    }

    await tester.pumpWidget(build(null));
    final Offset firstLocation = tester.getCenter(find.text('Source'));
    expect(find.text('Dragging'), findsNothing);
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    expect(find.text('Dragging'), findsOneWidget);
    await gesture.up();

    await tester.pumpWidget(build((int buttons) => buttons == kSecondaryButton));
    expect(find.text('Dragging'), findsNothing);
    final TestGesture gesture1 = await tester.startGesture(firstLocation, pointer: 8);
    await tester.pump();
    expect(find.text('Dragging'), findsNothing);
    await gesture1.up();

    await tester.pumpWidget(build((int buttons) => buttons & kTertiaryButton != 0 || buttons & kPrimaryButton != 0));
    expect(find.text('Dragging'), findsNothing);
    final TestGesture gesture2 = await tester.startGesture(firstLocation, pointer: 8);
    await tester.pump();
    expect(find.text('Dragging'), findsOneWidget);
    await gesture2.up();

    await tester.pumpWidget(build((int buttons) => false));
    expect(find.text('Dragging'), findsNothing);
    final TestGesture gesture3 = await tester.startGesture(firstLocation, pointer: 8);
    await tester.pump();
    expect(find.text('Dragging'), findsNothing);
    await gesture3.up();
  });
3597

3598
 testWidgets('throws error when both onWillAccept and onWillAcceptWithDetails are provided', (WidgetTester tester) async {
3599 3600 3601 3602 3603 3604 3605 3606
    expect(() => DragTarget<int>(
      builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
        return const SizedBox(height: 100.0, child: Text('Target'));
      },
      onWillAccept: (int? data) => true,
      onWillAcceptWithDetails: (DragTargetDetails<int> details) => false,
    ), throwsAssertionError);
 });
3607 3608
}

3609
Future<void> _testLongPressDraggableHapticFeedback({ required WidgetTester tester, required bool hapticFeedbackOnStart, required int expectedHapticFeedbackCount }) async {
3610 3611 3612
  bool onDragStartedCalled = false;

  int hapticFeedbackCalls = 0;
3613
  tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
3614 3615 3616
    if (methodCall.method == 'HapticFeedback.vibrate') {
      hapticFeedbackCalls++;
    }
3617
    return null;
3618 3619
  });

3620 3621
  await tester.pumpWidget(MaterialApp(
    home: LongPressDraggable<int>(
3622 3623 3624 3625 3626 3627
      data: 1,
      feedback: const Text('Dragging'),
      hapticFeedbackOnStart: hapticFeedbackOnStart,
      onDragStarted: () {
        onDragStartedCalled = true;
      },
3628
      child: const Text('Source'),
3629 3630 3631 3632 3633 3634 3635 3636
    ),
  ));

  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsNothing);
  expect(onDragStartedCalled, isFalse);

  final Offset firstLocation = tester.getCenter(find.text('Source'));
3637
  final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649
  await tester.pump();

  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsNothing);
  expect(onDragStartedCalled, isFalse);

  await tester.pump(kLongPressTimeout);

  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsOneWidget);
  expect(onDragStartedCalled, isTrue);
  expect(hapticFeedbackCalls, expectedHapticFeedbackCount);
3650 3651 3652 3653

  // Finish gesture to release resources.
  await gesture.up();
  await tester.pumpAndSettle();
3654 3655
}

3656
Future<void> _testChildAnchorFeedbackPosition({ required WidgetTester tester, double top = 0.0, double left = 0.0 }) async {
3657
  final List<int> accepted = <int>[];
3658
  final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
3659 3660
  int dragStartedCount = 0;

3661
  await tester.pumpWidget(
3662
    Stack(
3663 3664
      textDirection: TextDirection.ltr,
      children: <Widget>[
3665
        Positioned(
3666 3667 3668 3669
          left: left,
          top: top,
          right: 0.0,
          bottom: 0.0,
3670 3671
          child: MaterialApp(
            home: Column(
3672
              children: <Widget>[
3673
                Draggable<int>(
3674 3675 3676 3677 3678
                  data: 1,
                  feedback: const Text('Dragging'),
                  onDragStarted: () {
                    ++dragStartedCount;
                  },
3679
                  child: const Text('Source'),
3680
                ),
3681
                DragTarget<int>(
3682
                  builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
3683
                    return const SizedBox(height: 100.0, child: Text('Target'));
3684
                  },
3685
                  onAccept: accepted.add,
3686
                  onAcceptWithDetails: acceptedDetails.add,
3687 3688
                ),
              ],
3689
            ),
3690 3691 3692 3693 3694
          ),
        ),
      ],
    ),
  );
3695 3696

  expect(accepted, isEmpty);
3697
  expect(acceptedDetails, isEmpty);
3698 3699 3700 3701 3702
  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsNothing);
  expect(find.text('Target'), findsOneWidget);
  expect(dragStartedCount, 0);

3703
  final Offset firstLocation = tester.getCenter(find.text('Source'));
3704
  final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
3705 3706 3707
  await tester.pump();

  expect(accepted, isEmpty);
3708
  expect(acceptedDetails, isEmpty);
3709 3710 3711 3712 3713 3714
  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsOneWidget);
  expect(find.text('Target'), findsOneWidget);
  expect(dragStartedCount, 1);


3715
  final Offset secondLocation = tester.getBottomRight(find.text('Target'));
3716 3717 3718 3719
  await gesture.moveTo(secondLocation);
  await tester.pump();

  expect(accepted, isEmpty);
3720
  expect(acceptedDetails, isEmpty);
3721 3722 3723 3724 3725
  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsOneWidget);
  expect(find.text('Target'), findsOneWidget);
  expect(dragStartedCount, 1);

3726 3727
  final Offset feedbackTopLeft = tester.getTopLeft(find.text('Dragging'));
  final Offset sourceTopLeft = tester.getTopLeft(find.text('Source'));
3728
  final Offset dragOffset = secondLocation - firstLocation;
3729
  expect(feedbackTopLeft, equals(sourceTopLeft + dragOffset));
3730 3731
}

3732
class DragTargetData { }
3733

3734
class ExtendedDragTargetData extends DragTargetData { }