draggable_test.dart 101 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
import 'package:flutter/semantics.dart';
Adam Barth's avatar
Adam Barth committed
6
import 'package:flutter_test/flutter_test.dart';
7
import 'package:flutter/services.dart';
Adam Barth's avatar
Adam Barth committed
8
import 'package:flutter/material.dart';
9
import 'package:flutter/gestures.dart';
Hixie's avatar
Hixie committed
10

11 12
import 'semantics_tester.dart';

Hixie's avatar
Hixie committed
13
void main() {
14
  testWidgets('Drag and drop - control test', (WidgetTester tester) async {
15
    final List<int> accepted = <int>[];
16
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
17
    int dragStartedCount = 0;
18
    int moveCount = 0;
Hixie's avatar
Hixie committed
19

20 21
    await tester.pumpWidget(MaterialApp(
      home: Column(
22
        children: <Widget>[
23
          Draggable<int>(
24
            data: 1,
25 26
            child: const Text('Source'),
            feedback: const Text('Dragging'),
27 28 29
            onDragStarted: () {
              ++dragStartedCount;
            },
30
          ),
31
          DragTarget<int>(
32
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
33
              return Container(height: 100.0, child: const Text('Target'));
34
            },
35
            onMove: (_) => moveCount++,
36
            onAccept: accepted.add,
37
            onAcceptWithDetails: acceptedDetails.add,
38
          ),
39 40
        ],
      ),
41 42 43
    ));

    expect(accepted, isEmpty);
44
    expect(acceptedDetails, isEmpty);
45 46 47
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
48
    expect(dragStartedCount, 0);
49
    expect(moveCount, 0);
50

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

    expect(accepted, isEmpty);
56
    expect(acceptedDetails, isEmpty);
57 58 59
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
60
    expect(dragStartedCount, 1);
61
    expect(moveCount, 0);
62

63
    final Offset secondLocation = tester.getCenter(find.text('Target'));
64 65
    await gesture.moveTo(secondLocation);
    await tester.pump();
66 67

    expect(accepted, isEmpty);
68
    expect(acceptedDetails, isEmpty);
69 70 71
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
72
    expect(dragStartedCount, 1);
73
    expect(moveCount, 1);
74

75 76
    await gesture.up();
    await tester.pump();
77 78

    expect(accepted, equals(<int>[1]));
79 80
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
81 82 83
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
84
    expect(dragStartedCount, 1);
85
    expect(moveCount, 1);
Hixie's avatar
Hixie committed
86
  });
Hixie's avatar
Hixie committed
87

88 89 90 91 92 93
  testWidgets('Drag and drop - onLeave callback fires correctly', (WidgetTester tester) async {
    final Map<String,int> leftBehind = <String,int>{
      'Target 1': 0,
      'Target 2': 0,
    };

94 95
    await tester.pumpWidget(MaterialApp(
      home: Column(
96 97 98
        children: <Widget>[
          const Draggable<int>(
            data: 1,
99 100
            child: Text('Source'),
            feedback: Text('Dragging'),
101
          ),
102
          DragTarget<int>(
103
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
104
              return Container(height: 100.0, child: const Text('Target 1'));
105
            },
106
            onLeave: (Object? data) {
107
              if (data is int) {
108
                leftBehind['Target 1'] = leftBehind['Target 1']! + data;
109 110
              }
            },
111
          ),
112
          DragTarget<int>(
113
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
114
              return Container(height: 100.0, child: const Text('Target 2'));
115
            },
116
            onLeave: (Object? data) {
117
              if (data is int) {
118
                leftBehind['Target 2'] = leftBehind['Target 2']! + data;
119 120
              }
            },
121 122 123 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
          ),
        ],
      ),
    ));

    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));
161
  });
162

163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
  testWidgets('Drag and drop - onMove callback fires correctly', (WidgetTester tester) async {
    final Map<String,int> targetMoveCount = <String,int>{
      'Target 1': 0,
      'Target 2': 0,
    };

    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<int>(
            data: 1,
            child: Text('Source'),
            feedback: Text('Dragging'),
          ),
          DragTarget<int>(
178
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
179 180 181 182 183
              return Container(height: 100.0, child: const Text('Target 1'));
            },
            onMove: (DragTargetDetails<dynamic> details) {
              if (details.data is int) {
                targetMoveCount['Target 1'] =
184
                    targetMoveCount['Target 1']! + (details.data as int);
185 186 187 188
              }
            },
          ),
          DragTarget<int>(
189
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
190 191 192 193 194
              return Container(height: 100.0, child: const Text('Target 2'));
            },
            onMove: (DragTargetDetails<dynamic> details) {
              if (details.data is int) {
                targetMoveCount['Target 2'] =
195
                    targetMoveCount['Target 2']! + (details.data as int);
196 197 198 199 200 201 202 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
              }
            },
          ),
        ],
      ),
    ));

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

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

244 245
    await tester.pumpWidget(MaterialApp(
      home: Column(
246
        children: <Widget>[
247
          const Draggable<int>(
248
            data: 1,
249 250
            child: Text('Source'),
            feedback: Text('Dragging'),
251
          ),
252
          Stack(
253
            children: <Widget>[
254
              GestureDetector(
255 256 257 258
                behavior: HitTestBehavior.opaque,
                onTap: () {
                  events.add('tap');
                },
259 260 261 262 263
                child: Container(
                  child: const Text('Button'),
                ),
              ),
              DragTarget<int>(
264
                builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
265 266 267 268
                  return IgnorePointer(
                    child: Container(child: const Text('Target')),
                  );
                },
269
                onAccept: (int? data) {
270 271
                  events.add('drop');
                },
272 273 274
                onAcceptWithDetails: (DragTargetDetails<int> _) {
                  events.add('details');
                },
275 276
              ),
            ],
277
          ),
278 279
        ],
      ),
280 281 282 283 284 285 286 287 288 289 290
    ));

    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);
291
    await tester.tap(find.text('Button'));
292 293 294 295
    expect(events, equals(<String>['tap']));
    events.clear();

    expect(events, isEmpty);
296
    await tester.tap(find.text('Target'));
297 298 299 300 301 302
    expect(events, equals(<String>['tap']));
    events.clear();

    // drag and drop

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

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

    expect(events, isEmpty);
311 312
    await gesture.up();
    await tester.pump();
313
    expect(events, equals(<String>['drop', 'details']));
314 315 316 317 318
    events.clear();

    // drag and tap and drop

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

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

    expect(events, isEmpty);
327 328 329 330
    await tester.tap(find.text('Button'));
    await tester.tap(find.text('Target'));
    await gesture.up();
    await tester.pump();
331
    expect(events, equals(<String>['tap', 'tap', 'drop', 'details']));
332
    events.clear();
333 334
  });

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

339 340
    await tester.pumpWidget(MaterialApp(
      home: Column(
341
        children: <Widget>[
342
          Draggable<int>(
343
            data: 1,
344
            child: GestureDetector(
345 346 347
              behavior: HitTestBehavior.opaque,
              onTap: () {
                events.add('tap');
348
              },
349
              child: Container(child: const Text('Button')),
350
            ),
351
            feedback: const Text('Dragging'),
352
          ),
353
          DragTarget<int>(
354
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
355
              return const Text('Target');
356
            },
357
            onAccept: (int? data) {
358
              events.add('drop');
359
            },
360 361 362
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
363
          ),
364 365
        ],
      ),
366 367 368 369 370 371 372
    ));

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

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

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

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

    expect(events, isEmpty);
386 387
    await gesture.up();
    await tester.pump();
388
    expect(events, equals(<String>['drop', 'details']));
389
    events.clear();
390 391
  });

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

396 397
    await tester.pumpWidget(MaterialApp(
      home: Column(
398
        children: <Widget>[
399
          const LongPressDraggable<int>(
400
            data: 1,
401 402
            child: Text('Source'),
            feedback: Text('Dragging'),
403
          ),
404
          DragTarget<int>(
405
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
406
              return const Text('Target');
407
            },
408
            onAccept: (int? data) {
409
              events.add('drop');
410
            },
411 412 413
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
414
          ),
415 416
        ],
      ),
417 418 419 420 421 422 423
    ));

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

    expect(events, isEmpty);
424
    await tester.tap(find.text('Source'));
425 426 427
    expect(events, isEmpty);

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

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

    expect(events, isEmpty);
436 437
    await gesture.up();
    await tester.pump();
438
    expect(events, isEmpty);
439 440
  });

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

445 446
    await tester.pumpWidget(MaterialApp(
      home: Column(
447
        children: <Widget>[
448
          const Draggable<int>(
449
            data: 1,
450 451
            child: Text('Source'),
            feedback: Text('Dragging'),
452
          ),
453
          DragTarget<int>(
454
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
455
              return const Text('Target');
456
            },
457
            onAccept: (int? data) {
458
              events.add('drop');
459
            },
460 461 462
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
463
          ),
464 465
        ],
      ),
466
    ));
467

468 469 470
    expect(events, isEmpty);
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
471

472
    expect(events, isEmpty);
473
    await tester.tap(find.text('Source'));
474
    expect(events, isEmpty);
475

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

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

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

486
    expect(events, isEmpty);
487 488
    await gesture.up();
    await tester.pump();
489
    expect(events, equals(<String>['drop', 'details']));
490 491
  });

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

496 497
    await tester.pumpWidget(MaterialApp(
      home: ListView(
498
        dragStartBehavior: DragStartBehavior.down,
499
        children: <Widget>[
500
          DragTarget<int>(
501
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
502
              return const Text('Target');
503
            },
504
            onAccept: (int? data) {
505
              events.add('drop $data');
506
            },
507 508 509
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
510
          ),
511
          Container(height: 400.0),
512
          const Draggable<int>(
513
            data: 1,
514 515
            child: Text('H'),
            feedback: Text('Dragging'),
516
            affinity: Axis.horizontal,
517
          ),
518
          const Draggable<int>(
519
            data: 2,
520 521
            child: Text('V'),
            feedback: Text('Dragging'),
522
            affinity: Axis.vertical,
523
          ),
524 525 526 527
          Container(height: 500.0),
          Container(height: 500.0),
          Container(height: 500.0),
          Container(height: 500.0),
528 529
        ],
      ),
530 531 532 533 534 535 536 537 538 539 540
    ));

    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'));
541 542 543 544 545 546
    TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
    await gesture.up();
    await tester.pump();
547
    expect(events, equals(<String>['drop 2', 'details']));
548
    expect(tester.getCenter(find.text('Target')).dy, greaterThan(0.0));
549 550 551 552 553 554 555
    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'));
556 557 558 559 560 561 562 563
    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();
564
    expect(events, equals(<String>['drop 1', 'details']));
565
    expect(tester.getCenter(find.text('Target')).dy, greaterThan(0.0));
566 567 568 569 570 571 572 573
    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'));
574 575 576 577 578 579 580 581
    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();
582
    expect(events, equals(<String>['drop 2', 'details']));
583
    expect(tester.getCenter(find.text('Target')).dy, greaterThan(0.0));
584 585 586 587 588 589 590
    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'));
591 592 593 594 595 596
    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();
597
    expect(events, equals(<String>[]));
598
    expect(find.text('Target'), findsNothing);
599 600
    events.clear();
  });
601

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

606 607
    await tester.pumpWidget(MaterialApp(
      home: ListView(
608
        dragStartBehavior: DragStartBehavior.down,
609 610
        scrollDirection: Axis.horizontal,
        children: <Widget>[
611
          DragTarget<int>(
612
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
613
              return const Text('Target');
614
            },
615
            onAccept: (int? data) {
616
              events.add('drop $data');
617
            },
618 619 620
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
621
          ),
622
          Container(width: 400.0),
623
          const Draggable<int>(
624
            data: 1,
625 626
            child: Text('H'),
            feedback: Text('Dragging'),
627
            affinity: Axis.horizontal,
628
          ),
629
          const Draggable<int>(
630
            data: 2,
631 632
            child: Text('V'),
            feedback: Text('Dragging'),
633
            affinity: Axis.vertical,
634
          ),
635 636 637 638
          Container(width: 500.0),
          Container(width: 500.0),
          Container(width: 500.0),
          Container(width: 500.0),
639 640
        ],
      ),
641 642 643 644 645 646 647 648 649 650 651
    ));

    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'));
652 653 654 655 656 657
    TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
    await tester.pump();
    await gesture.moveTo(secondLocation);
    await tester.pump();
    await gesture.up();
    await tester.pump();
658
    expect(events, equals(<String>['drop 1', 'details']));
659
    expect(tester.getCenter(find.text('Target')).dx, greaterThan(0.0));
660 661 662 663 664 665 666
    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'));
667 668 669 670 671 672 673 674
    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();
675
    expect(events, equals(<String>['drop 2', 'details']));
676
    expect(tester.getCenter(find.text('Target')).dx, greaterThan(0.0));
677 678 679 680 681 682 683 684
    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'));
685 686 687 688 689 690 691 692
    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();
693
    expect(events, equals(<String>['drop 1', 'details']));
694
    expect(tester.getCenter(find.text('Target')).dx, greaterThan(0.0));
695 696 697 698 699 700 701
    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'));
702 703 704 705 706 707
    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();
708
    expect(events, equals(<String>[]));
709
    expect(find.text('Target'), findsNothing);
710 711
    events.clear();
  });
712

713 714 715 716
  group('Drag and drop - Draggables with a set axis only move along that axis', () {
    final List<String> events = <String>[];

    Widget build() {
717 718
      return MaterialApp(
        home: ListView(
719 720
          scrollDirection: Axis.horizontal,
          children: <Widget>[
721
            DragTarget<int>(
722
              builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
723 724
                return const Text('Target');
              },
725
              onAccept: (int? data) {
726
                events.add('drop $data');
727
              },
728 729 730
              onAcceptWithDetails: (DragTargetDetails<int> _) {
                events.add('details');
              },
731
            ),
732
            Container(width: 400.0),
733 734
            const Draggable<int>(
              data: 1,
735 736 737
              child: Text('H'),
              feedback: Text('H'),
              childWhenDragging: SizedBox(),
738 739 740 741
              axis: Axis.horizontal,
            ),
            const Draggable<int>(
              data: 2,
742 743 744
              child: Text('V'),
              feedback: Text('V'),
              childWhenDragging: SizedBox(),
745 746 747 748
              axis: Axis.vertical,
            ),
            const Draggable<int>(
              data: 3,
749 750 751
              child: Text('N'),
              feedback: Text('N'),
              childWhenDragging: SizedBox(),
752
            ),
753 754 755 756
            Container(width: 500.0),
            Container(width: 500.0),
            Container(width: 500.0),
            Container(width: 500.0),
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
          ],
        ),
      );
    }
    testWidgets('Null axis draggable moves along all axes', (WidgetTester tester) async {
      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);
    });

    testWidgets('Horizontal axis draggable moves horizontally', (WidgetTester tester) async {
      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);
    });

    testWidgets('Horizontal axis draggable does not move vertically', (WidgetTester tester) async {
      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);
    });

809
    testWidgets('Vertical axis draggable moves vertically', (WidgetTester tester) async {
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
      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);
    });

    testWidgets('Vertical axis draggable does not move horizontally', (WidgetTester tester) async {
      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);
    });
  });
842

843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994
  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,
              child: const Text('Source'),
              feedback: const Text('Dragging'),
              onDragUpdate: (DragUpdateDetails details) {
                dragDelta += details.delta;
                updated++;
              },
            ),
            Draggable<int>(
              data: 2,
              child: const Text('Vertical Source'),
              feedback: const Text('Vertical Dragging'),
              onDragUpdate: (DragUpdateDetails details) {
                dragDelta += details.delta;
                updated++;
              },
              axis: Axis.vertical,
            ),
            Draggable<int>(
              data: 3,
              child: const Text('Horizontal Source'),
              feedback: const Text('Horizontal Dragging'),
              onDragUpdate: (DragUpdateDetails details) {
                dragDelta += details.delta;
                updated++;
              },
              axis: Axis.horizontal,
            ),
          ],
        ),
      );
    }

    testWidgets('Null axis onDragUpdate called only if draggable moves in any direction', (WidgetTester tester) async {
      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);
    });

    testWidgets('Vertical axis onDragUpdate only called if draggable moves vertical', (WidgetTester tester) async {
      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);
    });

    testWidgets('Horizontal axis onDragUpdate only called if draggable moves horizontal', (WidgetTester tester) async {
      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);
    });
  });
995

996
  testWidgets('Drag and drop - onDraggableCanceled not called if dropped on accepting target', (WidgetTester tester) async {
997
    final List<int> accepted = <int>[];
998
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
999
    bool onDraggableCanceledCalled = false;
1000

1001 1002
    await tester.pumpWidget(MaterialApp(
      home: Column(
1003
        children: <Widget>[
1004
          Draggable<int>(
1005
            data: 1,
1006 1007
            child: const Text('Source'),
            feedback: const Text('Dragging'),
1008 1009
            onDraggableCanceled: (Velocity velocity, Offset offset) {
              onDraggableCanceledCalled = true;
1010
            },
1011
          ),
1012
          DragTarget<int>(
1013
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1014
              return Container(height: 100.0, child: const Text('Target'));
1015
            },
1016
            onAccept: accepted.add,
1017
            onAcceptWithDetails: acceptedDetails.add,
1018
          ),
1019 1020
        ],
      ),
1021 1022 1023
    ));

    expect(accepted, isEmpty);
1024
    expect(acceptedDetails, isEmpty);
1025 1026 1027 1028 1029
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

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

    expect(accepted, isEmpty);
1035
    expect(acceptedDetails, isEmpty);
1036 1037 1038 1039 1040
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1041
    final Offset secondLocation = tester.getCenter(find.text('Target'));
1042 1043
    await gesture.moveTo(secondLocation);
    await tester.pump();
1044 1045

    expect(accepted, isEmpty);
1046
    expect(acceptedDetails, isEmpty);
1047 1048 1049 1050 1051
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1052 1053
    await gesture.up();
    await tester.pump();
1054 1055

    expect(accepted, equals(<int>[1]));
1056 1057
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
1058 1059 1060 1061
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);
1062 1063
  });

1064
  testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target', (WidgetTester tester) async {
1065
    final List<int> accepted = <int>[];
1066
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1067
    bool onDraggableCanceledCalled = false;
1068 1069
    late Velocity onDraggableCanceledVelocity;
    late Offset onDraggableCanceledOffset;
1070

1071 1072
    await tester.pumpWidget(MaterialApp(
      home: Column(
1073
        children: <Widget>[
1074
          Draggable<int>(
1075
            data: 1,
1076 1077
            child: const Text('Source'),
            feedback: const Text('Dragging'),
1078 1079 1080 1081
            onDraggableCanceled: (Velocity velocity, Offset offset) {
              onDraggableCanceledCalled = true;
              onDraggableCanceledVelocity = velocity;
              onDraggableCanceledOffset = offset;
1082
            },
1083
          ),
1084
          DragTarget<int>(
1085
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1086
              return Container(
1087
                height: 100.0,
1088
                child: const Text('Target'),
1089 1090
              );
            },
1091
            onWillAccept: (int? data) => false,
1092 1093
            onAccept: accepted.add,
            onAcceptWithDetails: acceptedDetails.add,
1094
          ),
1095 1096
        ],
      ),
1097 1098 1099
    ));

    expect(accepted, isEmpty);
1100
    expect(acceptedDetails, isEmpty);
1101 1102 1103 1104 1105
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

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

    expect(accepted, isEmpty);
1111
    expect(acceptedDetails, isEmpty);
1112 1113 1114 1115 1116
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1117
    final Offset secondLocation = tester.getCenter(find.text('Target'));
1118 1119
    await gesture.moveTo(secondLocation);
    await tester.pump();
1120 1121

    expect(accepted, isEmpty);
1122
    expect(acceptedDetails, isEmpty);
1123 1124 1125 1126 1127
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1128 1129
    await gesture.up();
    await tester.pump();
1130 1131

    expect(accepted, isEmpty);
1132
    expect(acceptedDetails, isEmpty);
1133 1134 1135 1136 1137
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isTrue);
    expect(onDraggableCanceledVelocity, equals(Velocity.zero));
1138
    expect(onDraggableCanceledOffset, equals(Offset(secondLocation.dx, secondLocation.dy)));
1139
  });
1140

1141
  testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with correct velocity', (WidgetTester tester) async {
1142
    final List<int> accepted = <int>[];
1143
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1144
    bool onDraggableCanceledCalled = false;
1145 1146
    late Velocity onDraggableCanceledVelocity;
    late Offset onDraggableCanceledOffset;
1147

1148 1149 1150
    await tester.pumpWidget(MaterialApp(
      home: Column(children: <Widget>[
        Draggable<int>(
1151
          data: 1,
1152 1153
          child: const Text('Source'),
          feedback: const Text('Source'),
1154 1155 1156 1157
          onDraggableCanceled: (Velocity velocity, Offset offset) {
            onDraggableCanceledCalled = true;
            onDraggableCanceledVelocity = velocity;
            onDraggableCanceledOffset = offset;
1158
          },
1159
        ),
1160
        DragTarget<int>(
1161
          builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1162
            return Container(
1163
              height: 100.0,
1164
              child: const Text('Target'),
1165 1166
            );
          },
1167
          onWillAccept: (int? data) => false,
1168 1169 1170 1171 1172
          onAccept: accepted.add,
          onAcceptWithDetails: acceptedDetails.add,
        ),
      ],
    )));
1173 1174

    expect(accepted, isEmpty);
1175
    expect(acceptedDetails, isEmpty);
1176 1177 1178 1179 1180
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDraggableCanceledCalled, isFalse);

1181
    final Offset flingStart = tester.getTopLeft(find.text('Source'));
1182
    await tester.flingFrom(flingStart, const Offset(0.0, 100.0), 1000.0);
1183
    await tester.pump();
1184 1185

    expect(accepted, isEmpty);
1186
    expect(acceptedDetails, isEmpty);
1187 1188 1189 1190 1191 1192
    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));
1193
    expect(onDraggableCanceledOffset, equals(Offset(flingStart.dx, flingStart.dy) + const Offset(0.0, 100.0)));
1194
  });
1195

1196 1197
  testWidgets('Drag and drop - onDragEnd not called if dropped on non-accepting target', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
1198
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1199
    bool onDragEndCalled = false;
1200
    late DraggableDetails onDragEndDraggableDetails;
1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
            data: 1,
            child: const Text('Source'),
            feedback: const Text('Dragging'),
            onDragEnd: (DraggableDetails details) {
              onDragEndCalled = true;
              onDragEndDraggableDetails = details;
            },
          ),
          DragTarget<int>(
1214
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1215 1216 1217 1218 1219
              return Container(
                height: 100.0,
                child: const Text('Target'),
              );
            },
1220
            onWillAccept: (int? data) => false,
1221 1222
            onAccept: accepted.add,
            onAcceptWithDetails: acceptedDetails.add,
1223 1224 1225 1226 1227 1228
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);
1229
    expect(acceptedDetails, isEmpty);
1230 1231 1232 1233 1234 1235 1236 1237 1238 1239
    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);
1240
    expect(acceptedDetails, isEmpty);
1241 1242 1243 1244 1245 1246 1247 1248 1249 1250
    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);
1251
    expect(acceptedDetails, isEmpty);
1252 1253 1254 1255 1256 1257 1258 1259 1260
    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);
1261
    expect(acceptedDetails, isEmpty);
1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273
    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)));
  });

1274 1275 1276 1277 1278 1279 1280 1281 1282 1283
  testWidgets('Drag and drop - DragTarget rebuilds with and without rejected data when a rejected draggable enters and leaves', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<int>(
            data: 1,
            child: Text('Source'),
            feedback: Text('Dragging'),
          ),
          DragTarget<int>(
1284
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1285 1286 1287 1288 1289 1290 1291
              return Container(
                height: 100.0,
                child: rejects.isNotEmpty
                    ? const Text('Rejected')
                    : const Text('Target'),
              );
            },
1292
            onWillAccept: (int? data) => false,
1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341
          ),
        ],
      ),
    ));

    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 =
    await tester.startGesture(firstLocation, pointer: 7);
    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);
  });


  testWidgets('Drag and drop - Can drag and drop over a non-accepting target multiple times', (WidgetTester tester) async {
    int numberOfTimesOnDraggableCanceledCalled = 0;
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
            data: 1,
            child: const Text('Source'),
            feedback: const Text('Dragging'),
          onDraggableCanceled: (Velocity velocity, Offset offset) {
            numberOfTimesOnDraggableCanceledCalled++;
          },
          ),
          DragTarget<int>(
1342
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1343 1344 1345 1346 1347 1348 1349
              return Container(
                height: 100.0,
                child: rejects.isNotEmpty
                    ? const Text('Rejected')
                    : const Text('Target'),
              );
            },
1350
            onWillAccept: (int? data) => false,
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
          ),
        ],
      ),
    ));

    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 =
    await tester.startGesture(firstLocation, pointer: 7);
    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 =
    await tester.startGesture(firstLocation, pointer: 7);
    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);
  });

1410 1411
  testWidgets('Drag and drop - onDragCompleted not called if dropped on non-accepting target', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
1412
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1413 1414
    bool onDragCompletedCalled = false;

1415 1416
    await tester.pumpWidget(MaterialApp(
      home: Column(
1417
        children: <Widget>[
1418
          Draggable<int>(
1419 1420 1421 1422 1423
            data: 1,
            child: const Text('Source'),
            feedback: const Text('Dragging'),
            onDragCompleted: () {
              onDragCompletedCalled = true;
1424
            },
1425
          ),
1426
          DragTarget<int>(
1427
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1428
              return Container(
1429
                height: 100.0,
1430
                child: const Text('Target'),
1431 1432
              );
            },
1433
            onWillAccept: (int? data) => false,
1434 1435
            onAccept: accepted.add,
            onAcceptWithDetails: acceptedDetails.add,
1436
          ),
1437 1438
        ],
      ),
1439 1440 1441
    ));

    expect(accepted, isEmpty);
1442
    expect(acceptedDetails, isEmpty);
1443 1444 1445 1446 1447 1448 1449 1450 1451 1452
    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);
1453
    expect(acceptedDetails, isEmpty);
1454 1455 1456 1457 1458 1459 1460 1461 1462 1463
    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);
1464
    expect(acceptedDetails, isEmpty);
1465 1466 1467 1468 1469 1470 1471 1472 1473
    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);
1474
    expect(acceptedDetails, isEmpty);
1475 1476 1477 1478 1479 1480
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isFalse);
  });

1481 1482
  testWidgets('Drag and drop - onDragEnd called if dropped on accepting target', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
1483
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1484
    bool onDragEndCalled = false;
1485
    late DraggableDetails onDragEndDraggableDetails;
1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
            data: 1,
            child: const Text('Source'),
            feedback: const Text('Dragging'),
            onDragEnd: (DraggableDetails details) {
              onDragEndCalled = true;
              onDragEndDraggableDetails = details;
            },
          ),
          DragTarget<int>(
1499
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1500 1501 1502
              return Container(height: 100.0, child: const Text('Target'));
            },
            onAccept: accepted.add,
1503
            onAcceptWithDetails: acceptedDetails.add,
1504 1505 1506 1507 1508 1509
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);
1510
    expect(acceptedDetails, isEmpty);
1511 1512 1513 1514 1515 1516 1517 1518 1519 1520
    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);
1521
    expect(acceptedDetails, isEmpty);
1522 1523 1524 1525 1526 1527 1528 1529 1530 1531
    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);
1532
    expect(acceptedDetails, isEmpty);
1533 1534 1535 1536 1537 1538 1539 1540 1541
    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'));
1542 1543
    final Offset expectedDropOffset = Offset(droppedLocation.dx, secondLocation.dy - firstLocation.dy);

1544
    expect(accepted, equals(<int>[1]));
1545 1546
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
1547 1548 1549 1550 1551 1552 1553
    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));
1554
    expect(onDragEndDraggableDetails.offset, equals(expectedDropOffset));
1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572
  });

  testWidgets('DragTarget does not call onDragEnd when remove from the tree', (WidgetTester tester) async {
    final List<String> events = <String>[];
    Offset firstLocation, secondLocation;
    int timesOnDragEndCalled = 0;
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          Draggable<int>(
              data: 1,
              child: const Text('Source'),
              feedback: const Text('Dragging'),
              onDragEnd: (DraggableDetails details) {
                timesOnDragEndCalled++;
              },
          ),
          DragTarget<int>(
1573
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1574 1575
              return const Text('Target');
            },
1576
            onAccept: (int? data) {
1577 1578
              events.add('drop');
            },
1579 1580 1581
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
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
          ),
        ],
      ),
    ));

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

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

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

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

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

    await tester.pumpWidget(MaterialApp(
        home: Column(
            children: const <Widget>[
              Draggable<int>(
                  data: 1,
                  child: Text('Source'),
1611
                  feedback: Text('Dragging'),
1612
              ),
1613 1614
            ],
        ),
1615 1616 1617 1618 1619 1620 1621 1622
    ));

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

1623 1624
  testWidgets('Drag and drop - onDragCompleted called if dropped on accepting target', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
1625
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1626 1627
    bool onDragCompletedCalled = false;

1628 1629
    await tester.pumpWidget(MaterialApp(
      home: Column(
1630
        children: <Widget>[
1631
          Draggable<int>(
1632 1633 1634 1635 1636
            data: 1,
            child: const Text('Source'),
            feedback: const Text('Dragging'),
            onDragCompleted: () {
              onDragCompletedCalled = true;
1637
            },
1638
          ),
1639
          DragTarget<int>(
1640
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1641
              return Container(height: 100.0, child: const Text('Target'));
1642
            },
1643
            onAccept: accepted.add,
1644
            onAcceptWithDetails: acceptedDetails.add,
1645
          ),
1646 1647
        ],
      ),
1648 1649 1650
    ));

    expect(accepted, isEmpty);
1651
    expect(acceptedDetails, isEmpty);
1652 1653 1654 1655 1656 1657 1658 1659 1660 1661
    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);
1662
    expect(acceptedDetails, isEmpty);
1663 1664 1665 1666 1667 1668 1669 1670 1671 1672
    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);
1673
    expect(acceptedDetails, isEmpty);
1674 1675 1676 1677 1678 1679 1680 1681 1682
    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]));
1683 1684
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
1685 1686 1687 1688 1689 1690
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isTrue);
  });

1691
  testWidgets('Drag and drop - allow pass thru of unaccepted data test', (WidgetTester tester) async {
1692
    final List<int> acceptedInts = <int>[];
1693
    final List<DragTargetDetails<int>> acceptedIntsDetails = <DragTargetDetails<int>>[];
1694
    final List<double> acceptedDoubles = <double>[];
1695
    final List<DragTargetDetails<double>> acceptedDoublesDetails = <DragTargetDetails<double>>[];
1696

1697 1698
    await tester.pumpWidget(MaterialApp(
      home: Column(
1699
        children: <Widget>[
1700
          const Draggable<int>(
1701
            data: 1,
1702 1703
            child: Text('IntSource'),
            feedback: Text('IntDragging'),
1704
          ),
1705
          const Draggable<double>(
1706
            data: 1.0,
1707 1708
            child: Text('DoubleSource'),
            feedback: Text('DoubleDragging'),
1709
          ),
1710
          Stack(
1711
            children: <Widget>[
1712
              DragTarget<int>(
1713
                builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1714 1715
                  return IgnorePointer(
                    child: Container(
1716
                      height: 100.0,
1717 1718
                      child: const Text('Target1'),
                    ),
1719 1720
                  );
                },
1721
                onAccept: acceptedInts.add,
1722
                onAcceptWithDetails: acceptedIntsDetails.add,
1723
              ),
1724
              DragTarget<double>(
1725
                builder: (BuildContext context, List<double?> data, List<dynamic> rejects) {
1726 1727
                  return IgnorePointer(
                    child: Container(
1728
                      height: 100.0,
1729 1730
                      child: const Text('Target2'),
                    ),
1731 1732
                  );
                },
1733
                onAccept: acceptedDoubles.add,
1734
                onAcceptWithDetails: acceptedDoublesDetails.add,
1735
              ),
1736 1737 1738 1739
            ],
          ),
        ],
      ),
1740 1741 1742
    ));

    expect(acceptedInts, isEmpty);
1743
    expect(acceptedIntsDetails, isEmpty);
1744
    expect(acceptedDoubles, isEmpty);
1745
    expect(acceptedDoublesDetails, isEmpty);
1746 1747 1748 1749 1750 1751 1752
    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);

1753 1754 1755
    final Offset intLocation = tester.getCenter(find.text('IntSource'));
    final Offset doubleLocation = tester.getCenter(find.text('DoubleSource'));
    final Offset targetLocation = tester.getCenter(find.text('Target1'));
1756 1757

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

    expect(acceptedInts, isEmpty);
1762
    expect(acceptedIntsDetails, isEmpty);
1763
    expect(acceptedDoubles, isEmpty);
1764
    expect(acceptedDoublesDetails, isEmpty);
1765 1766 1767
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsOneWidget);

1768 1769
    await doubleGesture.moveTo(targetLocation);
    await tester.pump();
1770 1771

    expect(acceptedInts, isEmpty);
1772
    expect(acceptedIntsDetails, isEmpty);
1773
    expect(acceptedDoubles, isEmpty);
1774
    expect(acceptedDoublesDetails, isEmpty);
1775 1776 1777
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsOneWidget);

1778 1779
    await doubleGesture.up();
    await tester.pump();
1780 1781

    expect(acceptedInts, isEmpty);
1782
    expect(acceptedIntsDetails, isEmpty);
1783
    expect(acceptedDoubles, equals(<double>[1.0]));
1784 1785
    expect(acceptedDoublesDetails, hasLength(1));
    expect(acceptedDoublesDetails.first.offset, const Offset(112.0, 122.0));
1786 1787 1788 1789
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsNothing);

    acceptedDoubles.clear();
1790
    acceptedDoublesDetails.clear();
1791 1792

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

    expect(acceptedInts, isEmpty);
1797
    expect(acceptedIntsDetails, isEmpty);
1798
    expect(acceptedDoubles, isEmpty);
1799
    expect(acceptedDoublesDetails, isEmpty);
1800 1801 1802
    expect(find.text('IntDragging'), findsOneWidget);
    expect(find.text('DoubleDragging'), findsNothing);

1803 1804
    await intGesture.moveTo(targetLocation);
    await tester.pump();
1805 1806

    expect(acceptedInts, isEmpty);
1807
    expect(acceptedIntsDetails, isEmpty);
1808
    expect(acceptedDoubles, isEmpty);
1809
    expect(acceptedDoublesDetails, isEmpty);
1810 1811 1812
    expect(find.text('IntDragging'), findsOneWidget);
    expect(find.text('DoubleDragging'), findsNothing);

1813 1814
    await intGesture.up();
    await tester.pump();
1815 1816

    expect(acceptedInts, equals(<int>[1]));
1817 1818
    expect(acceptedIntsDetails, hasLength(1));
    expect(acceptedIntsDetails.first.offset, const Offset(184.0, 122.0));
1819
    expect(acceptedDoubles, isEmpty);
1820
    expect(acceptedDoublesDetails, isEmpty);
1821 1822 1823
    expect(find.text('IntDragging'), findsNothing);
    expect(find.text('DoubleDragging'), findsNothing);
  });
1824

1825
  testWidgets('Drag and drop - allow pass thru of unaccepted data twice test', (WidgetTester tester) async {
1826
    final List<DragTargetData> acceptedDragTargetDatas = <DragTargetData>[];
1827
    final List<DragTargetDetails<DragTargetData>> acceptedDragTargetDataDetails = <DragTargetDetails<DragTargetData>>[];
1828
    final List<ExtendedDragTargetData> acceptedExtendedDragTargetDatas = <ExtendedDragTargetData>[];
1829
    final List<DragTargetDetails<ExtendedDragTargetData>> acceptedExtendedDragTargetDataDetails = <DragTargetDetails<ExtendedDragTargetData>>[];
1830 1831 1832
    final DragTargetData dragTargetData = DragTargetData();
    await tester.pumpWidget(MaterialApp(
      home: Column(
1833
        children: <Widget>[
1834
          Draggable<DragTargetData>(
1835
            data: dragTargetData,
1836
            child: const Text('Source'),
1837
            feedback: const Text('Dragging'),
1838
          ),
1839
          Stack(
1840
            children: <Widget>[
1841
              DragTarget<DragTargetData>(
1842
                builder: (BuildContext context, List<DragTargetData?> data, List<dynamic> rejects) {
1843 1844
                  return IgnorePointer(
                    child: Container(
1845
                      height: 100.0,
1846 1847
                      child: const Text('Target1'),
                    ),
1848
                  );
1849
                }, onAccept: acceptedDragTargetDatas.add,
1850
                onAcceptWithDetails: acceptedDragTargetDataDetails.add,
1851
              ),
1852
              DragTarget<ExtendedDragTargetData>(
1853
                builder: (BuildContext context, List<ExtendedDragTargetData?> data, List<dynamic> rejects) {
1854 1855
                  return IgnorePointer(
                    child: Container(
1856
                      height: 100.0,
1857 1858
                      child: const Text('Target2'),
                    ),
1859 1860
                  );
                },
1861
                onAccept: acceptedExtendedDragTargetDatas.add,
1862
                onAcceptWithDetails: acceptedExtendedDragTargetDataDetails.add,
1863
              ),
1864 1865 1866 1867
            ],
          ),
        ],
      ),
1868
    ));
1869

1870 1871
    final Offset dragTargetLocation = tester.getCenter(find.text('Source'));
    final Offset targetLocation = tester.getCenter(find.text('Target1'));
1872

1873
    for (int i = 0; i < 2; i += 1) {
1874
      final TestGesture gesture = await tester.startGesture(dragTargetLocation);
1875 1876 1877 1878 1879
      await tester.pump();
      await gesture.moveTo(targetLocation);
      await tester.pump();
      await gesture.up();
      await tester.pump();
1880

1881
      expect(acceptedDragTargetDatas, equals(<DragTargetData>[dragTargetData]));
1882 1883
      expect(acceptedDragTargetDataDetails, hasLength(1));
      expect(acceptedDragTargetDataDetails.first.offset, const Offset(256.0, 74.0));
1884
      expect(acceptedExtendedDragTargetDatas, isEmpty);
1885
      expect(acceptedExtendedDragTargetDataDetails, isEmpty);
1886

1887
      acceptedDragTargetDatas.clear();
1888
      acceptedDragTargetDataDetails.clear();
1889
      await tester.pump();
1890
    }
1891
  });
1892

1893
  testWidgets('Drag and drop - maxSimultaneousDrags', (WidgetTester tester) async {
1894
    final List<int> accepted = <int>[];
1895
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
1896 1897

    Widget build(int maxSimultaneousDrags) {
1898 1899
      return MaterialApp(
        home: Column(
1900
          children: <Widget>[
1901
            Draggable<int>(
1902 1903
              data: 1,
              maxSimultaneousDrags: maxSimultaneousDrags,
1904
              child: const Text('Source'),
1905
              feedback: const Text('Dragging'),
1906
            ),
1907
            DragTarget<int>(
1908
              builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
1909
                return Container(height: 100.0, child: const Text('Target'));
1910
              },
1911
              onAccept: accepted.add,
1912
              onAcceptWithDetails: acceptedDetails.add,
1913
            ),
1914 1915
          ],
        ),
1916 1917 1918 1919 1920
      );
    }

    await tester.pumpWidget(build(0));

1921 1922
    final Offset firstLocation = tester.getCenter(find.text('Source'));
    final Offset secondLocation = tester.getCenter(find.text('Target'));
1923 1924

    expect(accepted, isEmpty);
1925
    expect(acceptedDetails, isEmpty);
1926 1927 1928 1929
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, isEmpty);
1934
    expect(acceptedDetails, isEmpty);
1935 1936 1937 1938 1939 1940 1941 1942 1943
    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);
1944
    expect(acceptedDetails, isEmpty);
1945 1946 1947 1948
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, isEmpty);
1953
    expect(acceptedDetails, isEmpty);
1954 1955 1956 1957
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, isEmpty);
1962
    expect(acceptedDetails, isEmpty);
1963 1964 1965 1966
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNWidgets(2));
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, isEmpty);
1971
    expect(acceptedDetails, isEmpty);
1972 1973 1974 1975 1976 1977 1978 1979 1980 1981
    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);
1982
    expect(acceptedDetails, isEmpty);
1983 1984 1985 1986 1987 1988 1989 1990
    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]));
1991 1992
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
1993 1994 1995 1996 1997 1998 1999 2000
    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]));
2001 2002 2003
    expect(acceptedDetails, hasLength(2));
    expect(acceptedDetails[0].offset, const Offset(256.0, 74.0));
    expect(acceptedDetails[1].offset, const Offset(256.0, 74.0));
2004 2005 2006 2007 2008 2009 2010 2011
    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]));
2012 2013 2014
    expect(acceptedDetails, hasLength(2));
    expect(acceptedDetails[0].offset, const Offset(256.0, 74.0));
    expect(acceptedDetails[1].offset, const Offset(256.0, 74.0));
2015 2016 2017 2018 2019
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
  });

2020 2021
  testWidgets('Draggable disposes recognizer', (WidgetTester tester) async {
    bool didTap = false;
2022
    await tester.pumpWidget(
2023
      Directionality(
2024
        textDirection: TextDirection.ltr,
2025
        child: Overlay(
2026
          initialEntries: <OverlayEntry>[
2027 2028
            OverlayEntry(
              builder: (BuildContext context) => GestureDetector(
2029 2030 2031
                onTap: () {
                  didTap = true;
                },
2032
                child: Draggable<Object>(
2033
                  child: Container(
2034 2035
                    color: const Color(0xFFFFFF00),
                  ),
2036
                  feedback: Container(
2037 2038 2039 2040 2041
                    width: 100.0,
                    height: 100.0,
                    color: const Color(0xFFFF0000),
                  ),
                ),
2042
              ),
2043 2044 2045 2046 2047
            ),
          ],
        ),
      ),
    );
2048

2049
    await tester.startGesture(const Offset(10.0, 10.0));
2050 2051 2052 2053
    expect(didTap, isFalse);

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

2058
  // Regression test for https://github.com/flutter/flutter/issues/6128.
2059
  testWidgets('Draggable plays nice with onTap', (WidgetTester tester) async {
2060
    await tester.pumpWidget(
2061
      Directionality(
2062
        textDirection: TextDirection.ltr,
2063
        child: Overlay(
2064
          initialEntries: <OverlayEntry>[
2065 2066
            OverlayEntry(
              builder: (BuildContext context) => GestureDetector(
2067
                onTap: () { /* registers a tap recognizer */ },
2068
                child: Draggable<Object>(
2069
                  child: Container(
2070 2071
                    color: const Color(0xFFFFFF00),
                  ),
2072
                  feedback: Container(
2073 2074 2075 2076 2077
                    width: 100.0,
                    height: 100.0,
                    color: const Color(0xFFFF0000),
                  ),
                ),
2078
              ),
2079 2080 2081 2082 2083
            ),
          ],
        ),
      ),
    );
2084

2085 2086
    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);
2087

2088
    await firstGesture.moveBy(const Offset(100.0, 0.0));
2089 2090
    await secondGesture.up();
  });
2091 2092

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

2096 2097
    await tester.pumpWidget(MaterialApp(
      home: Column(
2098
        children: <Widget>[
2099
          const Draggable<int>(
2100
            data: 1,
2101
            child: Text('Source'),
2102
            feedback: Text('Dragging'),
2103
          ),
2104
          DragTarget<int>(
2105
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2106
              return const Text('Target');
2107
            },
2108
            onAccept: (int? data) {
2109
              events.add('drop');
2110
            },
2111 2112 2113
            onAcceptWithDetails: (DragTargetDetails<int> _) {
              events.add('details');
            },
2114
          ),
2115 2116
        ],
      ),
2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127
    ));

    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'));
2128
    final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
2129 2130 2131 2132 2133 2134 2135 2136
    await tester.pump();

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

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

2137 2138
    await tester.pumpWidget(MaterialApp(
      home: Column(
2139
        children: const <Widget>[
2140
          Draggable<int>(
2141
            data: 1,
2142
            child: Text('Source'),
2143
            feedback: Text('Dragging'),
2144
          ),
2145 2146
        ],
      ),
2147 2148 2149 2150 2151 2152
    ));

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

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

2158 2159
    await tester.pumpWidget(MaterialApp(
      home: Column(
2160 2161 2162
        children: <Widget>[
          const Draggable<int>(
            data: 1,
2163
            child: Text('Source'),
2164
            feedback: Text('Dragging'),
2165
          ),
2166
          DragTarget<int>(
2167
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2168
              return Container(height: 100.0, child: const Text('Target'));
2169 2170
            },
            onAccept: accepted.add,
2171
            onAcceptWithDetails: acceptedDetails.add,
2172 2173 2174
          ),
        ],
      ),
2175 2176 2177
    ));

    expect(accepted, isEmpty);
2178
    expect(acceptedDetails, isEmpty);
2179 2180 2181 2182
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);

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

    expect(accepted, isEmpty);
2188
    expect(acceptedDetails, isEmpty);
2189 2190 2191 2192
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

2193 2194
    await tester.pumpWidget(MaterialApp(
      home: Column(
2195
        children: <Widget>[
2196
          DragTarget<int>(
2197
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2198
              return Container(height: 100.0, child: const Text('Target'));
2199 2200
            },
            onAccept: accepted.add,
2201
            onAcceptWithDetails: acceptedDetails.add,
2202 2203 2204
          ),
        ],
      ),
2205 2206 2207
    ));

    expect(accepted, isEmpty);
2208
    expect(acceptedDetails, isEmpty);
2209 2210 2211 2212
    expect(find.text('Source'), findsNothing);
    expect(find.text('Dragging'), findsOneWidget);
    expect(find.text('Target'), findsOneWidget);

2213
    final Offset secondLocation = tester.getCenter(find.text('Target'));
2214 2215 2216 2217
    await gesture.moveTo(secondLocation);
    await tester.pump();

    expect(accepted, isEmpty);
2218
    expect(acceptedDetails, isEmpty);
2219 2220 2221 2222 2223 2224 2225 2226
    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]));
2227 2228
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, const Offset(256.0, 26.0));
2229 2230 2231 2232 2233
    expect(find.text('Source'), findsNothing);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
  });

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

2237 2238 2239 2240
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: Center(
          child: GestureDetector(
2241 2242 2243
            onTap: () {
              events.add('tap');
            },
2244
            child: const LongPressDraggable<int>(
2245 2246
              feedback: Text('Feedback'),
              child: Text('X'),
2247 2248 2249 2250 2251 2252 2253 2254 2255 2256
            ),
          ),
        ),
      ),
    ));

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

2258 2259
  testWidgets('long-press draggable calls onDragEnd called if dropped on accepting target', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
2260
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
2261
    bool onDragEndCalled = false;
2262
    late DraggableDetails onDragEndDraggableDetails;
2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276

    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          LongPressDraggable<int>(
            data: 1,
            child: const Text('Source'),
            feedback: const Text('Dragging'),
            onDragEnd: (DraggableDetails details) {
              onDragEndCalled = true;
              onDragEndDraggableDetails = details;
            },
          ),
          DragTarget<int>(
2277
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2278 2279 2280
              return Container(height: 100.0, child: const Text('Target'));
            },
            onAccept: accepted.add,
2281
            onAcceptWithDetails: acceptedDetails.add,
2282 2283 2284 2285 2286 2287
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);
2288
    expect(acceptedDetails, isEmpty);
2289 2290 2291 2292 2293 2294 2295 2296 2297 2298
    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);
2299
    expect(acceptedDetails, isEmpty);
2300 2301 2302 2303 2304 2305 2306 2307
    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);
2308
    expect(acceptedDetails, isEmpty);
2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319
    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);
2320
    expect(acceptedDetails, isEmpty);
2321 2322 2323 2324 2325 2326 2327 2328 2329
    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'));
2330 2331
    final Offset expectedDropOffset = Offset(droppedLocation.dx, secondLocation.dy - firstLocation.dy);

2332
    expect(accepted, equals(<int>[1]));
2333 2334
    expect(acceptedDetails, hasLength(1));
    expect(acceptedDetails.first.offset, expectedDropOffset);
2335 2336 2337 2338 2339 2340 2341
    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));
2342
    expect(onDragEndDraggableDetails.offset, equals(expectedDropOffset));
2343 2344
  });

2345 2346
  testWidgets('long-press draggable calls onDragCompleted called if dropped on accepting target', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
2347
    final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
2348 2349
    bool onDragCompletedCalled = false;

2350 2351
    await tester.pumpWidget(MaterialApp(
      home: Column(
2352
        children: <Widget>[
2353
          LongPressDraggable<int>(
2354 2355 2356 2357 2358 2359 2360
            data: 1,
            child: const Text('Source'),
            feedback: const Text('Dragging'),
            onDragCompleted: () {
              onDragCompletedCalled = true;
            },
          ),
2361
          DragTarget<int>(
2362
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2363
              return Container(height: 100.0, child: const Text('Target'));
2364 2365
            },
            onAccept: accepted.add,
2366
            onAcceptWithDetails: acceptedDetails.add,
2367 2368 2369 2370 2371 2372
          ),
        ],
      ),
    ));

    expect(accepted, isEmpty);
2373
    expect(acceptedDetails, isEmpty);
2374 2375 2376 2377 2378 2379 2380 2381 2382 2383
    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);
2384
    expect(acceptedDetails, isEmpty);
2385 2386 2387 2388 2389 2390 2391 2392
    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);
2393
    expect(acceptedDetails, isEmpty);
2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404
    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);
2405
    expect(acceptedDetails, isEmpty);
2406 2407 2408 2409 2410 2411 2412 2413 2414
    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]));
2415
    expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
2416 2417 2418 2419 2420 2421 2422 2423 2424
    expect(find.text('Source'), findsOneWidget);
    expect(find.text('Dragging'), findsNothing);
    expect(find.text('Target'), findsOneWidget);
    expect(onDragCompletedCalled, isTrue);
  });

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

2425 2426
    await tester.pumpWidget(MaterialApp(
      home: LongPressDraggable<int>(
2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454
        data: 1,
        child: const Text('Source'),
        feedback: const Text('Dragging'),
        onDragStarted: () {
          onDragStartedCalled = true;
        },
      ),
    ));

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

    final Offset firstLocation = tester.getCenter(find.text('Source'));
    await tester.startGesture(firstLocation, pointer: 7);
    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);
  });

2455 2456 2457 2458 2459 2460 2461 2462
  testWidgets('long-press draggable calls Haptic Feedback onStart', (WidgetTester tester) async {
    await _testLongPressDraggableHapticFeedback(tester: tester, hapticFeedbackOnStart: true, expectedHapticFeedbackCount: 1);
  });

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

2463 2464 2465 2466 2467 2468 2469
  testWidgets('Drag feedback with child anchor positions correctly', (WidgetTester tester) async {
    await _testChildAnchorFeedbackPosition(tester: tester);
  });

  testWidgets('Drag feedback with child anchor within a non-global Overlay positions correctly', (WidgetTester tester) async {
    await _testChildAnchorFeedbackPosition(tester: tester, left: 100.0, top: 100.0);
  });
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 2498 2499 2500
  testWidgets('Drag feedback is put on root overlay with [rootOverlay] flag', (WidgetTester tester) async {
      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>[
            Container(
              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,
                        child: Text('Source'),
                        feedback: Text('Dragging'),
                        rootOverlay: true,
                      ),
                    );
                  }
                  throw UnsupportedError('Unsupported route: $settings');
                },
              ),
            ),
            DragTarget<int>(
2501
              builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534
                return Container(
                    height: 300.0, child: const Center(child: Text('Target 1')),
                );
              },
            ),
          ],
        ),
      ));

      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(
          find.descendant(
            of: find.byType(Overlay).first,
            matching: find.text('Dragging'),
          ),
          findsOneWidget);
      expect(
          find.descendant(
            of: find.byType(Overlay).last,
            matching: find.text('Dragging'),
          ),
          findsNothing);
    });
2535

2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650
  // Regression test for https://github.com/flutter/flutter/issues/72483
  testWidgets('Drag and drop - DragTarget<Object> can accept Draggable<int> data', (WidgetTester tester) async {
    final List<Object> accepted = <Object>[];
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<int>(
            data: 1,
            child: Text('Source'),
            feedback: Text('Dragging'),
          ),
          DragTarget<Object>(
            builder: (BuildContext context, List<Object?> data, List<dynamic> rejects) {
              return Container(height: 100.0, child: const Text('Target'));
            },
            onAccept: accepted.add,
          ),
        ],
      ),
    ));

    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]));
  });

  testWidgets('Drag and drop - DragTarget<int> can accept Draggable<Object> data when runtime type is int', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<Object>(
            data: 1,
            child: Text('Source'),
            feedback: Text('Dragging'),
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
              return Container(height: 100.0, child: const Text('Target'));
            },
            onAccept: accepted.add,
          ),
        ],
      ),
    ));

    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]));
  });

  testWidgets('Drag and drop - DragTarget<int> should not accept Draggable<Object> data when runtime type null', (WidgetTester tester) async {
    final List<int> accepted = <int>[];
    bool isReceiveNullDataForCheck = false;
    await tester.pumpWidget(MaterialApp(
      home: Column(
        children: <Widget>[
          const Draggable<Object>(
            child: Text('Source'),
            feedback: Text('Dragging'),
          ),
          DragTarget<int>(
            builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
              return Container(height: 100.0, child: const Text('Target'));
            },
            onAccept: accepted.add,
            onWillAccept: (int? data) {
              if (data == null)
                isReceiveNullDataForCheck = true;
              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);
  });

2651
  testWidgets('Drag and drop can contribute semantics', (WidgetTester tester) async {
2652 2653 2654
    final SemanticsTester semantics = SemanticsTester(tester);
    await tester.pumpWidget(MaterialApp(
        home: ListView(
2655
          scrollDirection: Axis.horizontal,
2656
          addSemanticIndexes: false,
2657
          children: <Widget>[
2658
            DragTarget<int>(
2659
              builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2660 2661 2662
                return const Text('Target');
              },
            ),
2663
            Container(width: 400.0),
2664 2665
            const Draggable<int>(
              data: 1,
2666 2667 2668
              child: Text('H'),
              feedback: Text('H'),
              childWhenDragging: SizedBox(),
2669 2670 2671 2672 2673
              axis: Axis.horizontal,
              ignoringFeedbackSemantics: false,
            ),
            const Draggable<int>(
              data: 2,
2674 2675 2676
              child: Text('V'),
              feedback: Text('V'),
              childWhenDragging: SizedBox(),
2677 2678 2679 2680 2681
              axis: Axis.vertical,
              ignoringFeedbackSemantics: false,
            ),
            const Draggable<int>(
              data: 3,
2682 2683 2684
              child: Text('N'),
              feedback: Text('N'),
              childWhenDragging: SizedBox(),
2685 2686 2687 2688
            ),
          ],
        ),
    ));
2689

2690
    expect(semantics, hasSemantics(
2691
      TestSemantics.root(
2692
        children: <TestSemantics>[
2693
          TestSemantics(
2694 2695 2696
            id: 1,
            textDirection: TextDirection.ltr,
            children: <TestSemantics>[
2697
              TestSemantics(
2698 2699
                id: 2,
                children: <TestSemantics>[
2700
                  TestSemantics(
2701
                    id: 3,
2702
                    flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
2703
                    children: <TestSemantics>[
2704
                      TestSemantics(
2705
                        id: 4,
2706
                        children: <TestSemantics>[
2707
                          TestSemantics(
2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736
                            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,
                              ),
                            ],
2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
    ), ignoreTransform: true, ignoreRect: true));

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

2756
    expect(semantics, hasSemantics(
2757
      TestSemantics.root(
2758
        children: <TestSemantics>[
2759
          TestSemantics(
2760 2761 2762
            id: 1,
            textDirection: TextDirection.ltr,
            children: <TestSemantics>[
2763
              TestSemantics(
2764 2765
                id: 2,
                children: <TestSemantics>[
2766
                  TestSemantics(
2767
                    id: 3,
2768
                    flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
2769
                    children: <TestSemantics>[
2770
                      TestSemantics(
2771
                        id: 4,
2772
                        children: <TestSemantics>[
2773
                          TestSemantics(
2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796
                            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.
                            ],
2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ],
    ), ignoreTransform: true, ignoreRect: true));
    semantics.dispose();
2809
  });
2810 2811
}

2812
Future<void> _testLongPressDraggableHapticFeedback({ required WidgetTester tester, required bool hapticFeedbackOnStart, required int expectedHapticFeedbackCount }) async {
2813 2814 2815 2816 2817 2818 2819 2820 2821
  bool onDragStartedCalled = false;

  int hapticFeedbackCalls = 0;
  SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
    if (methodCall.method == 'HapticFeedback.vibrate') {
      hapticFeedbackCalls++;
    }
  });

2822 2823
  await tester.pumpWidget(MaterialApp(
    home: LongPressDraggable<int>(
2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853
      data: 1,
      child: const Text('Source'),
      feedback: const Text('Dragging'),
      hapticFeedbackOnStart: hapticFeedbackOnStart,
      onDragStarted: () {
        onDragStartedCalled = true;
      },
    ),
  ));

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

  final Offset firstLocation = tester.getCenter(find.text('Source'));
  await tester.startGesture(firstLocation, pointer: 7);
  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);
}

2854
Future<void> _testChildAnchorFeedbackPosition({ required WidgetTester tester, double top = 0.0, double left = 0.0 }) async {
2855
  final List<int> accepted = <int>[];
2856
  final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
2857 2858
  int dragStartedCount = 0;

2859
  await tester.pumpWidget(
2860
    Stack(
2861 2862
      textDirection: TextDirection.ltr,
      children: <Widget>[
2863
        Positioned(
2864 2865 2866 2867
          left: left,
          top: top,
          right: 0.0,
          bottom: 0.0,
2868 2869
          child: MaterialApp(
            home: Column(
2870
              children: <Widget>[
2871
                Draggable<int>(
2872 2873 2874 2875 2876 2877 2878
                  data: 1,
                  child: const Text('Source'),
                  feedback: const Text('Dragging'),
                  onDragStarted: () {
                    ++dragStartedCount;
                  },
                ),
2879
                DragTarget<int>(
2880
                  builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
2881
                    return Container(height: 100.0, child: const Text('Target'));
2882 2883
                  },
                  onAccept: accepted.add,
2884
                  onAcceptWithDetails: acceptedDetails.add,
2885 2886
                ),
              ],
2887
            ),
2888 2889 2890 2891 2892
          ),
        ),
      ],
    ),
  );
2893 2894

  expect(accepted, isEmpty);
2895
  expect(acceptedDetails, isEmpty);
2896 2897 2898 2899 2900
  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsNothing);
  expect(find.text('Target'), findsOneWidget);
  expect(dragStartedCount, 0);

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

  expect(accepted, isEmpty);
2906
  expect(acceptedDetails, isEmpty);
2907 2908 2909 2910 2911 2912
  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsOneWidget);
  expect(find.text('Target'), findsOneWidget);
  expect(dragStartedCount, 1);


2913
  final Offset secondLocation = tester.getBottomRight(find.text('Target'));
2914 2915 2916 2917
  await gesture.moveTo(secondLocation);
  await tester.pump();

  expect(accepted, isEmpty);
2918
  expect(acceptedDetails, isEmpty);
2919 2920 2921 2922 2923
  expect(find.text('Source'), findsOneWidget);
  expect(find.text('Dragging'), findsOneWidget);
  expect(find.text('Target'), findsOneWidget);
  expect(dragStartedCount, 1);

2924 2925
  final Offset feedbackTopLeft = tester.getTopLeft(find.text('Dragging'));
  final Offset sourceTopLeft = tester.getTopLeft(find.text('Source'));
2926
  final Offset dragOffset = secondLocation - firstLocation;
2927
  expect(feedbackTopLeft, equals(sourceTopLeft + dragOffset));
2928 2929
}

2930
class DragTargetData { }
2931

2932
class ExtendedDragTargetData extends DragTargetData { }