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 7
import 'package:flutter/gestures.dart';
import 'package:quiver/testing/async.dart';

8
import '../flutter_test_alternative.dart';
9 10
import 'gesture_tester.dart';

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

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

22 23
  bool accepted = false;
  bool rejected = false;
24 25 26
}

void main() {
27
  setUp(ensureGestureBinding);
28 29

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

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

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

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

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

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

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

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

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

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

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

89 90 91 92 93 94 95 96 97 98 99 100
  // 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),
  );

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

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

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

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

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

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

    tap.dispose();
  });

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

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

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

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

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

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

    tap.dispose();
  });

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

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

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

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

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

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

    tap.dispose();
  });

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

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

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

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

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

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

    tap.dispose();
  });

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

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

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

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

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

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

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

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

    tap.dispose();
  });

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

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

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

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

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

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

    tap.dispose();
  });

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

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

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

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

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

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

    tap.dispose();
  });

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

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

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

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

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

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

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

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

    tap.dispose();

  });

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

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

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

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

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

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

    tap.dispose();
  });

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

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

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

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

    entry.resolve(GestureDisposition.accepted);
456
    expect(member.accepted, isTrue);
457 458

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

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

    tap.dispose();
  });

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

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

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

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

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

    entry.resolve(GestureDisposition.accepted);
500
    expect(member.accepted, isTrue);
501

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

    tap.dispose();
  });

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

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

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

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

      expect(member.accepted, isFalse);

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

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

    tap.dispose();
  });

542 543 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
  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();
  });
621 622 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

  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
743
    testGesture('A primary double tap recognizer does not form competition with a secondary tap recognizer', (GestureTester tester) {
744 745 746 747 748 749 750 751
      doubleTap.addPointer(down6);
      tapSecondary.addPointer(down6);
      tester.closeArena(down6.pointer);

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

Chris Bracken's avatar
Chris Bracken committed
752
    testGesture('A primary double tap recognizer forms competition with a primary tap recognizer', (GestureTester tester) {
753 754 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
      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();
  });
800
}