long_press_test.dart 24.1 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
import 'package:flutter/gestures.dart';
6
import 'package:flutter_test/flutter_test.dart';
7

8 9
import 'gesture_tester.dart';

10
// Down/move/up pair 1: normal tap sequence
11
const PointerDownEvent down = PointerDownEvent(
12
  pointer: 5,
13
  position: Offset(10, 10),
14 15
);

16
const PointerUpEvent up = PointerUpEvent(
17
  pointer: 5,
18 19 20 21 22 23
  position: Offset(11, 9),
);

const PointerMoveEvent move = PointerMoveEvent(
  pointer: 5,
  position: Offset(100, 200),
24 25
);

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
// Down/up pair 2: normal tap sequence far away from pair 1
const PointerDownEvent down2 = PointerDownEvent(
  pointer: 6,
  position: Offset(10, 10),
);

const PointerUpEvent up2 = PointerUpEvent(
  pointer: 6,
  position: Offset(11, 9),
);

// Down/up pair 3: tap sequence with secondary button
const PointerDownEvent down3 = PointerDownEvent(
  pointer: 7,
  position: Offset(30, 30),
  buttons: kSecondaryButton,
);

44
void main() {
45
  TestWidgetsFlutterBinding.ensureInitialized();
46

47
  group('Long press', () {
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
    late LongPressGestureRecognizer gesture;
    late List<String> recognized;

    void setUpHandlers() {
      gesture
        ..onLongPressDown = (LongPressDownDetails details) {
          recognized.add('down');
        }
        ..onLongPressCancel = () {
          recognized.add('cancel');
        }
        ..onLongPress = () {
          recognized.add('start');
        }
        ..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
          recognized.add('move');
        }
        ..onLongPressUp = () {
          recognized.add('end');
        };
    }
69

70
    setUp(() {
71 72 73
      recognized = <String>[];
      gesture = LongPressGestureRecognizer();
      setUpHandlers();
74
    });
75

76
    testGesture('Should recognize long press', (GestureTester tester) {
77
      gesture.addPointer(down);
78
      tester.closeArena(5);
79
      expect(recognized, const <String>[]);
80
      tester.route(down);
81
      expect(recognized, const <String>['down']);
82
      tester.async.elapse(const Duration(milliseconds: 300));
83
      expect(recognized, const <String>['down']);
84
      tester.async.elapse(const Duration(milliseconds: 700));
85 86 87
      expect(recognized, const <String>['down', 'start']);
      gesture.dispose();
      expect(recognized, const <String>['down', 'start']);
88 89
    });

90
    testGesture('Should recognize long press with altered duration', (GestureTester tester) {
91 92 93
      gesture = LongPressGestureRecognizer(duration: const Duration(milliseconds: 100));
      setUpHandlers();
      gesture.addPointer(down);
94
      tester.closeArena(5);
95
      expect(recognized, const <String>[]);
96
      tester.route(down);
97
      expect(recognized, const <String>['down']);
98
      tester.async.elapse(const Duration(milliseconds: 50));
99
      expect(recognized, const <String>['down']);
100
      tester.async.elapse(const Duration(milliseconds: 50));
101 102 103
      expect(recognized, const <String>['down', 'start']);
      gesture.dispose();
      expect(recognized, const <String>['down', 'start']);
104 105
    });

106
    testGesture('Up cancels long press', (GestureTester tester) {
107
      gesture.addPointer(down);
108
      tester.closeArena(5);
109
      expect(recognized, const <String>[]);
110
      tester.route(down);
111
      expect(recognized, const <String>['down']);
112
      tester.async.elapse(const Duration(milliseconds: 300));
113
      expect(recognized, const <String>['down']);
114
      tester.route(up);
115
      expect(recognized, const <String>['down', 'cancel']);
116
      tester.async.elapse(const Duration(seconds: 1));
117 118
      gesture.dispose();
      expect(recognized, const <String>['down', 'cancel']);
119 120 121
    });

    testGesture('Moving before accept cancels', (GestureTester tester) {
122
      gesture.addPointer(down);
123
      tester.closeArena(5);
124
      expect(recognized, const <String>[]);
125
      tester.route(down);
126
      expect(recognized, const <String>['down']);
127
      tester.async.elapse(const Duration(milliseconds: 300));
128
      expect(recognized, const <String>['down']);
129
      tester.route(move);
130
      expect(recognized, const <String>['down', 'cancel']);
131 132 133
      tester.async.elapse(const Duration(seconds: 1));
      tester.route(up);
      tester.async.elapse(const Duration(milliseconds: 300));
134 135 136
      expect(recognized, const <String>['down', 'cancel']);
      gesture.dispose();
      expect(recognized, const <String>['down', 'cancel']);
137 138 139
    });

    testGesture('Moving after accept is ok', (GestureTester tester) {
140
      gesture.addPointer(down);
141
      tester.closeArena(5);
142
      expect(recognized, const <String>[]);
143
      tester.route(down);
144
      expect(recognized, const <String>['down']);
145
      tester.async.elapse(const Duration(seconds: 1));
146
      expect(recognized, const <String>['down', 'start']);
147
      tester.route(move);
148
      expect(recognized, const <String>['down', 'start', 'move']);
149
      tester.route(up);
150
      expect(recognized, const <String>['down', 'start', 'move', 'end']);
151
      tester.async.elapse(const Duration(milliseconds: 300));
152 153 154
      expect(recognized, const <String>['down', 'start', 'move', 'end']);
      gesture.dispose();
      expect(recognized, const <String>['down', 'start', 'move', 'end']);
155 156 157 158 159
    });

    testGesture('Should recognize both tap down and long press', (GestureTester tester) {
      final TapGestureRecognizer tap = TapGestureRecognizer();
      tap.onTapDown = (_) {
160
        recognized.add('tap_down');
161 162 163
      };

      tap.addPointer(down);
164
      gesture.addPointer(down);
165
      tester.closeArena(5);
166
      expect(recognized, const <String>[]);
167
      tester.route(down);
168
      expect(recognized, const <String>['down']);
169
      tester.async.elapse(const Duration(milliseconds: 300));
170
      expect(recognized, const <String>['down', 'tap_down']);
171
      tester.async.elapse(const Duration(milliseconds: 700));
172
      expect(recognized, const <String>['down', 'tap_down', 'start']);
173
      tap.dispose();
174 175
      gesture.dispose();
      expect(recognized, const <String>['down', 'tap_down', 'start']);
176 177 178 179 180 181 182
    });

    testGesture('Drag start delayed by microtask', (GestureTester tester) {
      final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer();
      bool isDangerousStack = false;
      drag.onStart = (DragStartDetails details) {
        expect(isDangerousStack, isFalse);
183
        recognized.add('drag_start');
184 185 186
      };

      drag.addPointer(down);
187
      gesture.addPointer(down);
188
      tester.closeArena(5);
189
      expect(recognized, const <String>[]);
190
      tester.route(down);
191
      expect(recognized, const <String>['down']);
192
      tester.async.elapse(const Duration(milliseconds: 300));
193
      expect(recognized, const <String>['down']);
194
      isDangerousStack = true;
195
      gesture.dispose();
196
      isDangerousStack = false;
197
      expect(recognized, const <String>['down', 'cancel']);
198
      tester.async.flushMicrotasks();
199
      expect(recognized, const <String>['down', 'cancel', 'drag_start']);
200 201 202 203
      drag.dispose();
    });

    testGesture('Should recognize long press up', (GestureTester tester) {
204
      gesture.addPointer(down);
205
      tester.closeArena(5);
206
      expect(recognized, const <String>[]);
207
      tester.route(down); // kLongPressTimeout = 500;
208
      expect(recognized, const <String>['down']);
209
      tester.async.elapse(const Duration(milliseconds: 300));
210
      expect(recognized, const <String>['down']);
211
      tester.async.elapse(const Duration(milliseconds: 700));
212
      expect(recognized, const <String>['down', 'start']);
213
      tester.route(up);
214 215 216
      expect(recognized, const <String>['down', 'start', 'end']);
      gesture.dispose();
      expect(recognized, const <String>['down', 'start', 'end']);
217
    });
218 219

    testGesture('Should not recognize long press with more than one buttons', (GestureTester tester) {
220
      gesture.addPointer(const PointerDownEvent(
221 222
        pointer: 5,
        kind: PointerDeviceKind.mouse,
223
        buttons: kSecondaryMouseButton | kTertiaryButton,
224 225 226
        position: Offset(10, 10),
      ));
      tester.closeArena(5);
227
      expect(recognized, const <String>[]);
228
      tester.route(down);
229
      expect(recognized, const <String>[]);
230
      tester.async.elapse(const Duration(milliseconds: 1000));
231
      expect(recognized, const <String>[]);
232
      tester.route(up);
233 234 235
      expect(recognized, const <String>[]);
      gesture.dispose();
      expect(recognized, const <String>[]);
236 237 238
    });

    testGesture('Should cancel long press when buttons change before acceptance', (GestureTester tester) {
239
      gesture.addPointer(down);
240
      tester.closeArena(5);
241
      expect(recognized, const <String>[]);
242
      tester.route(down);
243
      expect(recognized, const <String>['down']);
244
      tester.async.elapse(const Duration(milliseconds: 300));
245
      expect(recognized, const <String>['down']);
246 247 248
      tester.route(const PointerMoveEvent(
        pointer: 5,
        kind: PointerDeviceKind.mouse,
249
        buttons: kTertiaryButton,
250 251
        position: Offset(10, 10),
      ));
252
      expect(recognized, const <String>['down', 'cancel']);
253
      tester.async.elapse(const Duration(milliseconds: 700));
254
      expect(recognized, const <String>['down', 'cancel']);
255
      tester.route(up);
256 257 258
      expect(recognized, const <String>['down', 'cancel']);
      gesture.dispose();
      expect(recognized, const <String>['down', 'cancel']);
259
    });
260 261

    testGesture('non-allowed pointer does not inadvertently reset the recognizer', (GestureTester tester) {
262 263
      gesture = LongPressGestureRecognizer(kind: PointerDeviceKind.touch);
      setUpHandlers();
264 265

      // Accept a long-press gesture
266
      gesture.addPointer(down);
267 268 269
      tester.closeArena(5);
      tester.route(down);
      tester.async.elapse(const Duration(milliseconds: 500));
270
      expect(recognized, const <String>['down', 'start']);
271 272

      // Add a non-allowed pointer (doesn't match the kind filter)
273
      gesture.addPointer(const PointerDownEvent(
274 275 276 277
        pointer: 101,
        kind: PointerDeviceKind.mouse,
        position: Offset(10, 10),
      ));
278
      expect(recognized, const <String>['down', 'start']);
279 280 281 282 283 284

      // Moving the primary pointer should result in a normal event
      tester.route(const PointerMoveEvent(
        pointer: 5,
        position: Offset(15, 15),
      ));
285
      expect(recognized, const <String>['down', 'start', 'move']);
286
      gesture.dispose();
287
    });
xster's avatar
xster committed
288
  });
289

290
  group('long press drag', () {
291
    late LongPressGestureRecognizer gesture;
292
    Offset? longPressDragUpdate;
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
    late List<String> recognized;

    void setUpHandlers() {
      gesture
        ..onLongPressDown = (LongPressDownDetails details) {
          recognized.add('down');
        }
        ..onLongPressCancel = () {
          recognized.add('cancel');
        }
        ..onLongPress = () {
          recognized.add('start');
        }
        ..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
          recognized.add('move');
          longPressDragUpdate = details.globalPosition;
        }
        ..onLongPressUp = () {
          recognized.add('end');
        };
    }
314 315

    setUp(() {
316 317 318
      gesture = LongPressGestureRecognizer();
      setUpHandlers();
      recognized = <String>[];
319 320 321
    });

    testGesture('Should recognize long press down', (GestureTester tester) {
322
      gesture.addPointer(down);
323
      tester.closeArena(5);
324
      expect(recognized, const <String>[]);
325
      tester.route(down);
326
      expect(recognized, const <String>['down']);
327
      tester.async.elapse(const Duration(milliseconds: 300));
328
      expect(recognized, const <String>['down']);
329
      tester.async.elapse(const Duration(milliseconds: 700));
330 331 332
      expect(recognized, const <String>['down', 'start']);
      gesture.dispose();
      expect(recognized, const <String>['down', 'start']);
333 334 335
    });

    testGesture('Short up cancels long press', (GestureTester tester) {
336
      gesture.addPointer(down);
337
      tester.closeArena(5);
338
      expect(recognized, const <String>[]);
339
      tester.route(down);
340
      expect(recognized, const <String>['down']);
341
      tester.async.elapse(const Duration(milliseconds: 300));
342
      expect(recognized, const <String>['down']);
343
      tester.route(up);
344
      expect(recognized, const <String>['down', 'cancel']);
345
      tester.async.elapse(const Duration(seconds: 1));
346 347 348
      expect(recognized, const <String>['down', 'cancel']);
      gesture.dispose();
      expect(recognized, const <String>['down', 'cancel']);
349 350 351
    });

    testGesture('Moving before accept cancels', (GestureTester tester) {
352
      gesture.addPointer(down);
353
      tester.closeArena(5);
354
      expect(recognized, const <String>[]);
355
      tester.route(down);
356
      expect(recognized, const <String>['down']);
357
      tester.async.elapse(const Duration(milliseconds: 300));
358
      expect(recognized, const <String>['down']);
359
      tester.route(move);
360
      expect(recognized, const <String>['down', 'cancel']);
361 362 363
      tester.async.elapse(const Duration(seconds: 1));
      tester.route(up);
      tester.async.elapse(const Duration(milliseconds: 300));
364 365 366
      expect(recognized, const <String>['down', 'cancel']);
      gesture.dispose();
      expect(recognized, const <String>['down', 'cancel']);
367 368 369
    });

    testGesture('Moving after accept does not cancel', (GestureTester tester) {
370
      gesture.addPointer(down);
371
      tester.closeArena(5);
372
      expect(recognized, const <String>[]);
373
      tester.route(down);
374
      expect(recognized, const <String>['down']);
375
      tester.async.elapse(const Duration(seconds: 1));
376
      expect(recognized, const <String>['down', 'start']);
377
      tester.route(move);
378
      expect(recognized, const <String>['down', 'start', 'move']);
379 380 381
      expect(longPressDragUpdate, const Offset(100, 200));
      tester.route(up);
      tester.async.elapse(const Duration(milliseconds: 300));
382 383 384
      expect(recognized, const <String>['down', 'start', 'move', 'end']);
      gesture.dispose();
      expect(recognized, const <String>['down', 'start', 'move', 'end']);
385
    });
386
  });
387

388 389 390 391 392 393 394 395
  group('Enforce consistent-button restriction:', () {
    // In sequence between `down` and `up` but with buttons changed
    const PointerMoveEvent moveR = PointerMoveEvent(
      pointer: 5,
      buttons: kSecondaryButton,
      position: Offset(10, 10),
    );

396
    late LongPressGestureRecognizer gesture;
397 398 399
    final List<String> recognized = <String>[];

    setUp(() {
400 401 402 403 404 405 406
      gesture = LongPressGestureRecognizer()
        ..onLongPressDown = (LongPressDownDetails details) {
          recognized.add('down');
        }
        ..onLongPressCancel = () {
          recognized.add('cancel');
        }
407 408 409 410 411 412 413 414 415
        ..onLongPressStart = (LongPressStartDetails details) {
          recognized.add('start');
        }
        ..onLongPressEnd = (LongPressEndDetails details) {
          recognized.add('end');
        };
    });

    tearDown(() {
416
      gesture.dispose();
417 418 419 420 421
      recognized.clear();
    });

    testGesture('Should cancel long press when buttons change before acceptance', (GestureTester tester) {
      // First press
422
      gesture.addPointer(down);
423 424 425 426
      tester.closeArena(down.pointer);
      tester.route(down);
      tester.async.elapse(const Duration(milliseconds: 300));
      tester.route(moveR);
427
      expect(recognized, const <String>['down', 'cancel']);
428 429
      tester.async.elapse(const Duration(milliseconds: 700));
      tester.route(up);
430
      expect(recognized, const <String>['down', 'cancel']);
431 432 433 434
    });

    testGesture('Buttons change before acceptance should not prevent the next long press', (GestureTester tester) {
      // First press
435
      gesture.addPointer(down);
436 437
      tester.closeArena(down.pointer);
      tester.route(down);
438
      expect(recognized, <String>['down']);
439 440
      tester.async.elapse(const Duration(milliseconds: 300));
      tester.route(moveR);
441
      expect(recognized, <String>['down', 'cancel']);
442 443 444 445 446
      tester.async.elapse(const Duration(milliseconds: 700));
      tester.route(up);
      recognized.clear();

      // Second press
447
      gesture.addPointer(down2);
448 449
      tester.closeArena(down2.pointer);
      tester.route(down2);
450
      expect(recognized, <String>['down']);
451
      tester.async.elapse(const Duration(milliseconds: 1000));
452
      expect(recognized, <String>['down', 'start']);
453 454 455 456 457 458 459 460
      recognized.clear();

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

    testGesture('Should cancel long press when buttons change after acceptance', (GestureTester tester) {
      // First press
461
      gesture.addPointer(down);
462 463 464
      tester.closeArena(down.pointer);
      tester.route(down);
      tester.async.elapse(const Duration(milliseconds: 1000));
465
      expect(recognized, <String>['down', 'start']);
466 467 468 469 470 471 472 473 474 475
      recognized.clear();

      tester.route(moveR);
      expect(recognized, <String>[]);
      tester.route(up);
      expect(recognized, <String>[]);
    });

    testGesture('Buttons change after acceptance should not prevent the next long press', (GestureTester tester) {
      // First press
476
      gesture.addPointer(down);
477 478 479 480 481 482 483 484
      tester.closeArena(down.pointer);
      tester.route(down);
      tester.async.elapse(const Duration(milliseconds: 1000));
      tester.route(moveR);
      tester.route(up);
      recognized.clear();

      // Second press
485
      gesture.addPointer(down2);
486 487 488
      tester.closeArena(down2.pointer);
      tester.route(down2);
      tester.async.elapse(const Duration(milliseconds: 1000));
489
      expect(recognized, <String>['down', 'start']);
490 491 492 493 494 495 496
      recognized.clear();

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

497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
  testGesture('Can filter long press based on device kind', (GestureTester tester) {
    final LongPressGestureRecognizer mouseLongPress = LongPressGestureRecognizer(kind: PointerDeviceKind.mouse);

    bool mouseLongPressDown = false;
    mouseLongPress.onLongPress = () {
      mouseLongPressDown = true;
    };

    const PointerDownEvent mouseDown = PointerDownEvent(
      pointer: 5,
      position: Offset(10, 10),
      kind: PointerDeviceKind.mouse,
    );
    const PointerDownEvent touchDown = PointerDownEvent(
      pointer: 5,
      position: Offset(10, 10),
    );

    // Touch events shouldn't be recognized.
    mouseLongPress.addPointer(touchDown);
    tester.closeArena(5);
    expect(mouseLongPressDown, isFalse);
    tester.route(touchDown);
    expect(mouseLongPressDown, isFalse);
    tester.async.elapse(const Duration(seconds: 2));
    expect(mouseLongPressDown, isFalse);

    // Mouse events are still recognized.
    mouseLongPress.addPointer(mouseDown);
    tester.closeArena(5);
    expect(mouseLongPressDown, isFalse);
    tester.route(mouseDown);
    expect(mouseLongPressDown, isFalse);
    tester.async.elapse(const Duration(seconds: 2));
    expect(mouseLongPressDown, isTrue);

    mouseLongPress.dispose();
  });
535 536 537 538 539 540 541 542 543 544

  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 long press recognizers do not form
    // competition with a tap gesture recognizer listening on a different button.

    final List<String> recognized = <String>[];
545 546 547
    late TapGestureRecognizer tapPrimary;
    late TapGestureRecognizer tapSecondary;
    late LongPressGestureRecognizer longPress;
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
    setUp(() {
      tapPrimary = TapGestureRecognizer()
        ..onTapDown = (TapDownDetails details) {
          recognized.add('tapPrimary');
        };
      tapSecondary = TapGestureRecognizer()
        ..onSecondaryTapDown = (TapDownDetails details) {
          recognized.add('tapSecondary');
        };
      longPress = LongPressGestureRecognizer()
        ..onLongPressStart = (_) {
          recognized.add('longPress');
        };
    });

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

Chris Bracken's avatar
Chris Bracken committed
570
    testGesture('A primary long press recognizer does not form competition with a secondary tap recognizer', (GestureTester tester) {
571 572 573 574 575 576 577 578
      longPress.addPointer(down3);
      tapSecondary.addPointer(down3);
      tester.closeArena(down3.pointer);

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

Chris Bracken's avatar
Chris Bracken committed
579
    testGesture('A primary long press recognizer forms competition with a primary tap recognizer', (GestureTester tester) {
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 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
      longPress.addPointer(down);
      tapPrimary.addPointer(down);
      tester.closeArena(down.pointer);

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

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

  testGesture('A secondary long press should not trigger primary', (GestureTester tester) {
    final List<String> recognized = <String>[];
    final LongPressGestureRecognizer longPress = LongPressGestureRecognizer()
      ..onLongPressStart = (LongPressStartDetails details) {
        recognized.add('primaryStart');
      }
      ..onLongPress = () {
        recognized.add('primary');
      }
      ..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
        recognized.add('primaryUpdate');
      }
      ..onLongPressEnd = (LongPressEndDetails details) {
        recognized.add('primaryEnd');
      }
      ..onLongPressUp = () {
        recognized.add('primaryUp');
      };

    const PointerDownEvent down2 = PointerDownEvent(
      pointer: 2,
      buttons: kSecondaryButton,
      position: Offset(30.0, 30.0),
    );

    const PointerMoveEvent move2 = PointerMoveEvent(
      pointer: 2,
      buttons: kSecondaryButton,
      position: Offset(100, 200),
    );

    const PointerUpEvent up2 = PointerUpEvent(
      pointer: 2,
      position: Offset(100, 201),
    );

    longPress.addPointer(down2);
    tester.closeArena(2);
    tester.route(down2);
    tester.async.elapse(const Duration(milliseconds: 700));
    tester.route(move2);
    tester.route(up2);
    expect(recognized, <String>[]);
    longPress.dispose();
    recognized.clear();
  });
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

  testGesture('A tertiary long press should not trigger primary or secondary', (GestureTester tester) {
    final List<String> recognized = <String>[];
    final LongPressGestureRecognizer longPress = LongPressGestureRecognizer()
      ..onLongPressStart = (LongPressStartDetails details) {
        recognized.add('primaryStart');
      }
      ..onLongPress = () {
        recognized.add('primary');
      }
      ..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
        recognized.add('primaryUpdate');
      }
      ..onLongPressEnd = (LongPressEndDetails details) {
        recognized.add('primaryEnd');
      }
      ..onLongPressUp = () {
        recognized.add('primaryUp');
      }
      ..onSecondaryLongPressStart = (LongPressStartDetails details) {
        recognized.add('secondaryStart');
      }
      ..onSecondaryLongPress = () {
        recognized.add('secondary');
      }
      ..onSecondaryLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
        recognized.add('secondaryUpdate');
      }
      ..onSecondaryLongPressEnd = (LongPressEndDetails details) {
        recognized.add('secondaryEnd');
      }
      ..onSecondaryLongPressUp = () {
        recognized.add('secondaryUp');
      };

    const PointerDownEvent down2 = PointerDownEvent(
      pointer: 2,
      buttons: kTertiaryButton,
      position: Offset(30.0, 30.0),
    );

    const PointerMoveEvent move2 = PointerMoveEvent(
      pointer: 2,
      buttons: kTertiaryButton,
      position: Offset(100, 200),
    );

    const PointerUpEvent up2 = PointerUpEvent(
      pointer: 2,
      position: Offset(100, 201),
    );

    longPress.addPointer(down2);
    tester.closeArena(2);
    tester.route(down2);
    tester.async.elapse(const Duration(milliseconds: 700));
    tester.route(move2);
    tester.route(up2);
    expect(recognized, <String>[]);
    longPress.dispose();
    recognized.clear();
  });
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714

  testWidgets('LongPressGestureRecognizer asserts when kind and supportedDevices are both set', (WidgetTester tester) async {
    expect(
      () {
        LongPressGestureRecognizer(
          kind: PointerDeviceKind.touch,
          supportedDevices: <PointerDeviceKind>{ PointerDeviceKind.touch },
        );
      },
      throwsA(
        isA<AssertionError>().having((AssertionError error) => error.toString(),
        'description', contains('kind == null || supportedDevices == null')),
      ),
    );
  });
715
}