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

5 6
// @dart = 2.8

7 8 9
import 'package:flutter/gestures.dart';
import 'package:quiver/testing/async.dart';

10
import '../flutter_test_alternative.dart';
11 12
import 'gesture_tester.dart';

13
class TestGestureArenaMember extends GestureArenaMember {
14
  @override
15
  void acceptGesture(int key) {
16 17
    accepted = true;
  }
18 19

  @override
20
  void rejectGesture(int key) {
21 22
    rejected = true;
  }
23

24 25
  bool accepted = false;
  bool rejected = false;
26 27 28
}

void main() {
29
  setUp(ensureGestureBinding);
30 31

  // Down/up pair 1: normal tap sequence
32
  const PointerDownEvent down1 = PointerDownEvent(
33
    pointer: 1,
34
    position: Offset(10.0, 10.0),
35 36
  );

37
  const PointerUpEvent up1 = PointerUpEvent(
38
    pointer: 1,
39
    position: Offset(11.0, 9.0),
40 41 42
  );

  // Down/up pair 2: normal tap sequence close to pair 1
43
  const PointerDownEvent down2 = PointerDownEvent(
44
    pointer: 2,
45
    position: Offset(12.0, 12.0),
46 47
  );

48
  const PointerUpEvent up2 = PointerUpEvent(
49
    pointer: 2,
50
    position: Offset(13.0, 11.0),
51 52 53
  );

  // Down/up pair 3: normal tap sequence far away from pair 1
54
  const PointerDownEvent down3 = PointerDownEvent(
55
    pointer: 3,
56
    position: Offset(130.0, 130.0),
57 58
  );

59
  const PointerUpEvent up3 = PointerUpEvent(
60
    pointer: 3,
61
    position: Offset(131.0, 129.0),
62 63 64
  );

  // Down/move/up sequence 4: intervening motion
65
  const PointerDownEvent down4 = PointerDownEvent(
66
    pointer: 4,
67
    position: Offset(10.0, 10.0),
68 69
  );

70
  const PointerMoveEvent move4 = PointerMoveEvent(
71
    pointer: 4,
72
    position: Offset(25.0, 25.0),
73 74
  );

75
  const PointerUpEvent up4 = PointerUpEvent(
76
    pointer: 4,
77
    position: Offset(25.0, 25.0),
78 79
  );

80
  // Down/up pair 5: normal tap sequence identical to pair 1
81
  const PointerDownEvent down5 = PointerDownEvent(
82
    pointer: 5,
83
    position: Offset(10.0, 10.0),
84 85
  );

86
  const PointerUpEvent up5 = PointerUpEvent(
87
    pointer: 5,
88
    position: Offset(11.0, 9.0),
89 90
  );

91 92 93 94 95 96 97 98 99 100 101 102
  // Down/up pair 6: normal tap sequence close to pair 1 but on secondary button
  const PointerDownEvent down6 = PointerDownEvent(
    pointer: 6,
    position: Offset(10.0, 10.0),
    buttons: kSecondaryMouseButton,
  );

  const PointerUpEvent up6 = PointerUpEvent(
    pointer: 6,
    position: Offset(11.0, 9.0),
  );

103
  testGesture('Should recognize double tap', (GestureTester tester) {
104
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
105 106 107 108 109 110 111

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

    tap.addPointer(down1);
112
    tester.closeArena(1);
113
    expect(doubleTapRecognized, isFalse);
114
    tester.route(down1);
115 116
    expect(doubleTapRecognized, isFalse);

117
    tester.route(up1);
118
    expect(doubleTapRecognized, isFalse);
119
    GestureBinding.instance.gestureArena.sweep(1);
120 121
    expect(doubleTapRecognized, isFalse);

122
    tester.async.elapse(const Duration(milliseconds: 100));
123
    tap.addPointer(down2);
124
    tester.closeArena(2);
125
    expect(doubleTapRecognized, isFalse);
126
    tester.route(down2);
127 128
    expect(doubleTapRecognized, isFalse);

129
    tester.route(up2);
130
    expect(doubleTapRecognized, isTrue);
131
    GestureBinding.instance.gestureArena.sweep(2);
132 133 134 135 136
    expect(doubleTapRecognized, isTrue);

    tap.dispose();
  });

137
  testGesture('Inter-tap distance cancels double tap', (GestureTester tester) {
138
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
139 140 141 142 143 144 145

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

    tap.addPointer(down1);
146
    tester.closeArena(1);
147
    expect(doubleTapRecognized, isFalse);
148
    tester.route(down1);
149 150
    expect(doubleTapRecognized, isFalse);

151
    tester.route(up1);
152
    expect(doubleTapRecognized, isFalse);
153
    GestureBinding.instance.gestureArena.sweep(1);
154 155 156
    expect(doubleTapRecognized, isFalse);

    tap.addPointer(down3);
157
    tester.closeArena(3);
158
    expect(doubleTapRecognized, isFalse);
159
    tester.route(down3);
160 161
    expect(doubleTapRecognized, isFalse);

162
    tester.route(up3);
163
    expect(doubleTapRecognized, isFalse);
164
    GestureBinding.instance.gestureArena.sweep(3);
165 166 167 168 169
    expect(doubleTapRecognized, isFalse);

    tap.dispose();
  });

170
  testGesture('Intra-tap distance cancels double tap', (GestureTester tester) {
171
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
172 173 174 175 176 177 178

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

    tap.addPointer(down4);
179
    tester.closeArena(4);
180
    expect(doubleTapRecognized, isFalse);
181
    tester.route(down4);
182 183
    expect(doubleTapRecognized, isFalse);

184
    tester.route(move4);
185
    expect(doubleTapRecognized, isFalse);
186
    tester.route(up4);
187
    expect(doubleTapRecognized, isFalse);
188
    GestureBinding.instance.gestureArena.sweep(4);
189 190 191
    expect(doubleTapRecognized, isFalse);

    tap.addPointer(down1);
192
    tester.closeArena(1);
193
    expect(doubleTapRecognized, isFalse);
194
    tester.route(down2);
195 196
    expect(doubleTapRecognized, isFalse);

197
    tester.route(up1);
198
    expect(doubleTapRecognized, isFalse);
199
    GestureBinding.instance.gestureArena.sweep(1);
200 201 202 203 204
    expect(doubleTapRecognized, isFalse);

    tap.dispose();
  });

205
  testGesture('Inter-tap delay cancels double tap', (GestureTester tester) {
206
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
207 208 209 210 211 212

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

213 214 215 216 217
    tap.addPointer(down1);
    tester.closeArena(1);
    expect(doubleTapRecognized, isFalse);
    tester.route(down1);
    expect(doubleTapRecognized, isFalse);
218

219 220 221 222
    tester.route(up1);
    expect(doubleTapRecognized, isFalse);
    GestureBinding.instance.gestureArena.sweep(1);
    expect(doubleTapRecognized, isFalse);
223

224
    tester.async.elapse(const Duration(milliseconds: 5000));
225 226 227 228 229
    tap.addPointer(down2);
    tester.closeArena(2);
    expect(doubleTapRecognized, isFalse);
    tester.route(down2);
    expect(doubleTapRecognized, isFalse);
230

231 232 233 234
    tester.route(up2);
    expect(doubleTapRecognized, isFalse);
    GestureBinding.instance.gestureArena.sweep(2);
    expect(doubleTapRecognized, isFalse);
235 236 237 238

    tap.dispose();
  });

239
  testGesture('Inter-tap delay resets double tap, allowing third tap to be a double-tap', (GestureTester tester) {
240
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
241 242 243 244 245 246

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

247 248 249 250 251
    tap.addPointer(down1);
    tester.closeArena(1);
    expect(doubleTapRecognized, isFalse);
    tester.route(down1);
    expect(doubleTapRecognized, isFalse);
252

253 254 255 256
    tester.route(up1);
    expect(doubleTapRecognized, isFalse);
    GestureBinding.instance.gestureArena.sweep(1);
    expect(doubleTapRecognized, isFalse);
257

258
    tester.async.elapse(const Duration(milliseconds: 5000));
259 260 261 262 263
    tap.addPointer(down2);
    tester.closeArena(2);
    expect(doubleTapRecognized, isFalse);
    tester.route(down2);
    expect(doubleTapRecognized, isFalse);
264

265 266 267 268
    tester.route(up2);
    expect(doubleTapRecognized, isFalse);
    GestureBinding.instance.gestureArena.sweep(2);
    expect(doubleTapRecognized, isFalse);
269

270
    tester.async.elapse(const Duration(milliseconds: 100));
271 272 273 274 275
    tap.addPointer(down5);
    tester.closeArena(5);
    expect(doubleTapRecognized, isFalse);
    tester.route(down5);
    expect(doubleTapRecognized, isFalse);
276

277 278 279 280
    tester.route(up5);
    expect(doubleTapRecognized, isTrue);
    GestureBinding.instance.gestureArena.sweep(5);
    expect(doubleTapRecognized, isTrue);
281 282 283 284

    tap.dispose();
  });

285
  testGesture('Intra-tap delay does not cancel double tap', (GestureTester tester) {
286
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
287 288 289 290 291 292

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

293 294 295 296 297
    tap.addPointer(down1);
    tester.closeArena(1);
    expect(doubleTapRecognized, isFalse);
    tester.route(down1);
    expect(doubleTapRecognized, isFalse);
298

299
    tester.async.elapse(const Duration(milliseconds: 1000));
300 301 302 303
    tester.route(up1);
    expect(doubleTapRecognized, isFalse);
    GestureBinding.instance.gestureArena.sweep(1);
    expect(doubleTapRecognized, isFalse);
304

305 306 307 308 309
    tap.addPointer(down2);
    tester.closeArena(2);
    expect(doubleTapRecognized, isFalse);
    tester.route(down2);
    expect(doubleTapRecognized, isFalse);
310

311 312 313 314
    tester.route(up2);
    expect(doubleTapRecognized, isTrue);
    GestureBinding.instance.gestureArena.sweep(2);
    expect(doubleTapRecognized, isTrue);
315 316 317 318

    tap.dispose();
  });

319
  testGesture('Should not recognize two overlapping taps', (GestureTester tester) {
320
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
321 322 323 324 325 326 327

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

    tap.addPointer(down1);
328
    tester.closeArena(1);
329
    expect(doubleTapRecognized, isFalse);
330
    tester.route(down1);
331 332 333
    expect(doubleTapRecognized, isFalse);

    tap.addPointer(down2);
334
    tester.closeArena(2);
335
    expect(doubleTapRecognized, isFalse);
336
    tester.route(down1);
337 338
    expect(doubleTapRecognized, isFalse);

339
    tester.route(up1);
340
    expect(doubleTapRecognized, isFalse);
341
    GestureBinding.instance.gestureArena.sweep(1);
342 343
    expect(doubleTapRecognized, isFalse);

344
    tester.route(up2);
345
    expect(doubleTapRecognized, isFalse);
346
    GestureBinding.instance.gestureArena.sweep(2);
347 348 349 350 351
    expect(doubleTapRecognized, isFalse);

    tap.dispose();
  });

352
  testGesture('Should recognize one tap of group followed by second tap', (GestureTester tester) {
353
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
354 355 356 357 358 359 360

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

    tap.addPointer(down1);
361
    tester.closeArena(1);
362
    expect(doubleTapRecognized, isFalse);
363
    tester.route(down1);
364 365 366
    expect(doubleTapRecognized, isFalse);

    tap.addPointer(down2);
367
    tester.closeArena(2);
368
    expect(doubleTapRecognized, isFalse);
369
    tester.route(down1);
370 371
    expect(doubleTapRecognized, isFalse);

372
    tester.route(up1);
373
    expect(doubleTapRecognized, isFalse);
374
    GestureBinding.instance.gestureArena.sweep(1);
375 376
    expect(doubleTapRecognized, isFalse);

377
    tester.route(up2);
378
    expect(doubleTapRecognized, isFalse);
379
    GestureBinding.instance.gestureArena.sweep(2);
380 381
    expect(doubleTapRecognized, isFalse);

382
    tester.async.elapse(const Duration(milliseconds: 100));
383
    tap.addPointer(down1);
384
    tester.closeArena(1);
385
    expect(doubleTapRecognized, isFalse);
386
    tester.route(down1);
387 388
    expect(doubleTapRecognized, isFalse);

389
    tester.route(up1);
390
    expect(doubleTapRecognized, isTrue);
391
    GestureBinding.instance.gestureArena.sweep(1);
392 393 394 395 396 397
    expect(doubleTapRecognized, isTrue);

    tap.dispose();

  });

398
  testGesture('Should cancel on arena reject during first tap', (GestureTester tester) {
399
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
400 401 402 403 404 405 406

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

    tap.addPointer(down1);
407
    final TestGestureArenaMember member = TestGestureArenaMember();
408
    final GestureArenaEntry entry = GestureBinding.instance.gestureArena.add(1, member);
409
    tester.closeArena(1);
410
    expect(doubleTapRecognized, isFalse);
411
    tester.route(down1);
412 413
    expect(doubleTapRecognized, isFalse);

414
    tester.route(up1);
415 416
    expect(doubleTapRecognized, isFalse);
    entry.resolve(GestureDisposition.accepted);
417
    expect(member.accepted, isTrue);
418
    expect(doubleTapRecognized, isFalse);
419
    GestureBinding.instance.gestureArena.sweep(1);
420 421 422
    expect(doubleTapRecognized, isFalse);

    tap.addPointer(down2);
423
    tester.closeArena(2);
424
    expect(doubleTapRecognized, isFalse);
425
    tester.route(down2);
426 427
    expect(doubleTapRecognized, isFalse);

428
    tester.route(up2);
429
    expect(doubleTapRecognized, isFalse);
430
    GestureBinding.instance.gestureArena.sweep(2);
431 432 433 434 435
    expect(doubleTapRecognized, isFalse);

    tap.dispose();
  });

436
  testGesture('Should cancel on arena reject between taps', (GestureTester tester) {
437
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
438 439 440 441 442 443 444

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

    tap.addPointer(down1);
445
    final TestGestureArenaMember member = TestGestureArenaMember();
446
    final GestureArenaEntry entry = GestureBinding.instance.gestureArena.add(1, member);
447
    tester.closeArena(1);
448
    expect(doubleTapRecognized, isFalse);
449
    tester.route(down1);
450 451
    expect(doubleTapRecognized, isFalse);

452
    tester.route(up1);
453
    expect(doubleTapRecognized, isFalse);
454
    GestureBinding.instance.gestureArena.sweep(1);
455 456 457
    expect(doubleTapRecognized, isFalse);

    entry.resolve(GestureDisposition.accepted);
458
    expect(member.accepted, isTrue);
459 460

    tap.addPointer(down2);
461
    tester.closeArena(2);
462
    expect(doubleTapRecognized, isFalse);
463
    tester.route(down2);
464 465
    expect(doubleTapRecognized, isFalse);

466
    tester.route(up2);
467
    expect(doubleTapRecognized, isFalse);
468
    GestureBinding.instance.gestureArena.sweep(2);
469 470 471 472 473
    expect(doubleTapRecognized, isFalse);

    tap.dispose();
  });

474
  testGesture('Should cancel on arena reject during last tap', (GestureTester tester) {
475
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
476 477 478 479 480 481 482

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

    tap.addPointer(down1);
483
    final TestGestureArenaMember member = TestGestureArenaMember();
484
    final GestureArenaEntry entry = GestureBinding.instance.gestureArena.add(1, member);
485
    tester.closeArena(1);
486
    expect(doubleTapRecognized, isFalse);
487
    tester.route(down1);
488 489
    expect(doubleTapRecognized, isFalse);

490
    tester.route(up1);
491
    expect(doubleTapRecognized, isFalse);
492
    GestureBinding.instance.gestureArena.sweep(1);
493 494 495
    expect(doubleTapRecognized, isFalse);

    tap.addPointer(down2);
496
    tester.closeArena(2);
497
    expect(doubleTapRecognized, isFalse);
498
    tester.route(down2);
499 500 501
    expect(doubleTapRecognized, isFalse);

    entry.resolve(GestureDisposition.accepted);
502
    expect(member.accepted, isTrue);
503

504
    tester.route(up2);
505
    expect(doubleTapRecognized, isFalse);
506
    GestureBinding.instance.gestureArena.sweep(2);
507 508 509 510 511
    expect(doubleTapRecognized, isFalse);

    tap.dispose();
  });

512
  testGesture('Passive gesture should trigger on double tap cancel', (GestureTester tester) {
513
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();
514 515 516 517 518 519

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

520
    FakeAsync().run((FakeAsync async) {
521
      tap.addPointer(down1);
522
      final TestGestureArenaMember member = TestGestureArenaMember();
523
      GestureBinding.instance.gestureArena.add(1, member);
524
      tester.closeArena(1);
525
      expect(doubleTapRecognized, isFalse);
526
      tester.route(down1);
527 528
      expect(doubleTapRecognized, isFalse);

529
      tester.route(up1);
530
      expect(doubleTapRecognized, isFalse);
531
      GestureBinding.instance.gestureArena.sweep(1);
532 533 534 535
      expect(doubleTapRecognized, isFalse);

      expect(member.accepted, isFalse);

Ian Hickson's avatar
Ian Hickson committed
536
      async.elapse(const Duration(milliseconds: 5000));
537 538 539 540 541 542 543

      expect(member.accepted, isTrue);
    });

    tap.dispose();
  });

544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
  testGesture('Should not recognize two over-rapid taps', (GestureTester tester) {
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

    tap.addPointer(down1);
    tester.closeArena(1);
    expect(doubleTapRecognized, isFalse);
    tester.route(down1);
    expect(doubleTapRecognized, isFalse);

    tester.route(up1);
    expect(doubleTapRecognized, isFalse);
    GestureBinding.instance.gestureArena.sweep(1);
    expect(doubleTapRecognized, isFalse);

    tester.async.elapse(const Duration(milliseconds: 10));
    tap.addPointer(down2);
    tester.closeArena(2);
    expect(doubleTapRecognized, isFalse);
    tester.route(down2);
    expect(doubleTapRecognized, isFalse);

    tester.route(up2);
    expect(doubleTapRecognized, isFalse);
    GestureBinding.instance.gestureArena.sweep(2);
    expect(doubleTapRecognized, isFalse);

    tap.dispose();
  });

  testGesture('Over-rapid taps resets double tap, allowing third tap to be a double-tap', (GestureTester tester) {
    final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();

    bool doubleTapRecognized = false;
    tap.onDoubleTap = () {
      doubleTapRecognized = true;
    };

    tap.addPointer(down1);
    tester.closeArena(1);
    expect(doubleTapRecognized, isFalse);
    tester.route(down1);
    expect(doubleTapRecognized, isFalse);

    tester.route(up1);
    expect(doubleTapRecognized, isFalse);
    GestureBinding.instance.gestureArena.sweep(1);
    expect(doubleTapRecognized, isFalse);

    tester.async.elapse(const Duration(milliseconds: 10));
    tap.addPointer(down2);
    tester.closeArena(2);
    expect(doubleTapRecognized, isFalse);
    tester.route(down2);
    expect(doubleTapRecognized, isFalse);

    tester.route(up2);
    expect(doubleTapRecognized, isFalse);
    GestureBinding.instance.gestureArena.sweep(2);
    expect(doubleTapRecognized, isFalse);

    tester.async.elapse(const Duration(milliseconds: 100));
    tap.addPointer(down5);
    tester.closeArena(5);
    expect(doubleTapRecognized, isFalse);
    tester.route(down5);
    expect(doubleTapRecognized, isFalse);

    tester.route(up5);
    expect(doubleTapRecognized, isTrue);
    GestureBinding.instance.gestureArena.sweep(5);
    expect(doubleTapRecognized, isTrue);

    tap.dispose();
  });
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744

  group('Enforce consistent-button restriction:', () {
    testGesture('Button change should interrupt existing sequence', (GestureTester tester) {
      // Down1 -> down6 (different button from 1) -> down2 (same button as 1)
      // Down1 and down2 could've been a double tap, but is interrupted by down 6.

      const Duration interval = Duration(milliseconds: 100);
      assert(interval * 2 < kDoubleTapTimeout);
      assert(interval > kDoubleTapMinTime);

      final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();

      bool doubleTapRecognized = false;
      tap.onDoubleTap = () {
        doubleTapRecognized = true;
      };

      tap.addPointer(down1);
      tester.closeArena(1);
      tester.route(down1);
      tester.route(up1);
      GestureBinding.instance.gestureArena.sweep(1);

      tester.async.elapse(interval);

      tap.addPointer(down6);
      tester.closeArena(6);
      tester.route(down6);
      tester.route(up6);
      GestureBinding.instance.gestureArena.sweep(6);

      tester.async.elapse(interval);
      expect(doubleTapRecognized, isFalse);

      tap.addPointer(down2);
      tester.closeArena(2);
      tester.route(down2);
      tester.route(up2);
      GestureBinding.instance.gestureArena.sweep(2);

      expect(doubleTapRecognized, isFalse);

      tap.dispose();
    });

    testGesture('Button change should start a valid sequence', (GestureTester tester) {
      // Down6 -> down1 (different button from 6) -> down2 (same button as 1)

      const Duration interval = Duration(milliseconds: 100);
      assert(interval * 2 < kDoubleTapTimeout);
      assert(interval > kDoubleTapMinTime);

      final DoubleTapGestureRecognizer tap = DoubleTapGestureRecognizer();

      bool doubleTapRecognized = false;
      tap.onDoubleTap = () {
        doubleTapRecognized = true;
      };

      tap.addPointer(down6);
      tester.closeArena(6);
      tester.route(down6);
      tester.route(up6);
      GestureBinding.instance.gestureArena.sweep(6);

      tester.async.elapse(interval);

      tap.addPointer(down1);
      tester.closeArena(1);
      tester.route(down1);
      tester.route(up1);
      GestureBinding.instance.gestureArena.sweep(1);

      expect(doubleTapRecognized, isFalse);
      tester.async.elapse(interval);

      tap.addPointer(down2);
      tester.closeArena(2);
      tester.route(down2);
      tester.route(up2);
      GestureBinding.instance.gestureArena.sweep(2);

      expect(doubleTapRecognized, isTrue);

      tap.dispose();
    });
  });

  group('Recognizers listening on different buttons do not form competition:', () {
    // This test is assisted by tap recognizers. If a tap gesture has
    // no competing recognizers, a pointer down event triggers its onTapDown
    // immediately; if there are competitors, onTapDown is triggered after a
    // timeout.
    // The following tests make sure that double tap recognizers do not form
    // competition with a tap gesture recognizer listening on a different button.

    final List<String> recognized = <String>[];
    TapGestureRecognizer tapPrimary;
    TapGestureRecognizer tapSecondary;
    DoubleTapGestureRecognizer doubleTap;
    setUp(() {
      tapPrimary = TapGestureRecognizer()
        ..onTapDown = (TapDownDetails details) {
          recognized.add('tapPrimary');
        };
      tapSecondary = TapGestureRecognizer()
        ..onSecondaryTapDown = (TapDownDetails details) {
          recognized.add('tapSecondary');
        };
      doubleTap = DoubleTapGestureRecognizer()
        ..onDoubleTap = () {
          recognized.add('doubleTap');
        };
    });

    tearDown(() {
      recognized.clear();
      tapPrimary.dispose();
      tapSecondary.dispose();
      doubleTap.dispose();
    });

Chris Bracken's avatar
Chris Bracken committed
745
    testGesture('A primary double tap recognizer does not form competition with a secondary tap recognizer', (GestureTester tester) {
746 747 748 749 750 751 752 753
      doubleTap.addPointer(down6);
      tapSecondary.addPointer(down6);
      tester.closeArena(down6.pointer);

      tester.route(down6);
      expect(recognized, <String>['tapSecondary']);
    });

Chris Bracken's avatar
Chris Bracken committed
754
    testGesture('A primary double tap recognizer forms competition with a primary tap recognizer', (GestureTester tester) {
755 756 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
      doubleTap.addPointer(down1);
      tapPrimary.addPointer(down1);
      tester.closeArena(down1.pointer);

      tester.route(down1);
      expect(recognized, <String>[]);

      tester.async.elapse(const Duration(milliseconds: 300));
      expect(recognized, <String>['tapPrimary']);
    });
  });

  testGesture('A secondary double tap should not trigger primary', (GestureTester tester) {
    final List<String> recognized = <String>[];
    final DoubleTapGestureRecognizer doubleTap = DoubleTapGestureRecognizer()
      ..onDoubleTap = () {
        recognized.add('primary');
      };

    // Down/up pair 7: normal tap sequence close to pair 6
    const PointerDownEvent down7 = PointerDownEvent(
      pointer: 7,
      position: Offset(10.0, 10.0),
      buttons: kSecondaryMouseButton,
    );

    const PointerUpEvent up7 = PointerUpEvent(
      pointer: 7,
      position: Offset(11.0, 9.0),
    );

    doubleTap.addPointer(down6);
    tester.closeArena(6);
    tester.route(down6);
    tester.route(up6);
    GestureBinding.instance.gestureArena.sweep(6);

    tester.async.elapse(const Duration(milliseconds: 100));
    doubleTap.addPointer(down7);
    tester.closeArena(7);
    tester.route(down7);
    tester.route(up7);
    expect(recognized, <String>[]);

    recognized.clear();
    doubleTap.dispose();
  });
802
}