scale_test.dart 22.6 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
import 'dart:math' as math;

7
import 'package:flutter/gestures.dart';
8
import 'package:flutter_test/flutter_test.dart';
9

10 11
import 'gesture_tester.dart';

12
void main() {
13
  setUp(ensureGestureBinding);
14

15
  testGesture('Should recognize scale gestures', (GestureTester tester) {
16 17
    final ScaleGestureRecognizer scale = ScaleGestureRecognizer();
    final TapGestureRecognizer tap = TapGestureRecognizer();
18 19

    bool didStartScale = false;
20
    Offset? updatedFocalPoint;
21
    scale.onStart = (ScaleStartDetails details) {
22
      didStartScale = true;
23
      updatedFocalPoint = details.focalPoint;
24 25
    };

26 27 28
    double? updatedScale;
    double? updatedHorizontalScale;
    double? updatedVerticalScale;
29
    Offset? updatedDelta;
30 31
    scale.onUpdate = (ScaleUpdateDetails details) {
      updatedScale = details.scale;
32 33
      updatedHorizontalScale = details.horizontalScale;
      updatedVerticalScale = details.verticalScale;
34
      updatedFocalPoint = details.focalPoint;
35
      updatedDelta = details.delta;
36 37 38
    };

    bool didEndScale = false;
39
    scale.onEnd = (ScaleEndDetails details) {
40 41 42 43 44 45 46 47
      didEndScale = true;
    };

    bool didTap = false;
    tap.onTap = () {
      didTap = true;
    };

48
    final TestPointer pointer1 = TestPointer(1);
49

50
    final PointerDownEvent down = pointer1.down(Offset.zero);
51 52 53
    scale.addPointer(down);
    tap.addPointer(down);

54
    tester.closeArena(1);
55 56 57
    expect(didStartScale, isFalse);
    expect(updatedScale, isNull);
    expect(updatedFocalPoint, isNull);
58
    expect(updatedDelta, isNull);
59 60 61 62
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

    // One-finger panning
63
    tester.route(down);
64 65 66
    expect(didStartScale, isFalse);
    expect(updatedScale, isNull);
    expect(updatedFocalPoint, isNull);
67
    expect(updatedDelta, isNull);
68 69 70
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

71
    tester.route(pointer1.move(const Offset(20.0, 30.0)));
72 73
    expect(didStartScale, isTrue);
    didStartScale = false;
74
    expect(updatedFocalPoint, const Offset(20.0, 30.0));
75 76 77
    updatedFocalPoint = null;
    expect(updatedScale, 1.0);
    updatedScale = null;
78 79
    expect(updatedDelta, const Offset(20.0, 30.0));
    updatedDelta = null;
80 81 82 83
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

    // Two-finger scaling
84
    final TestPointer pointer2 = TestPointer(2);
85
    final PointerDownEvent down2 = pointer2.down(const Offset(10.0, 20.0));
86 87
    scale.addPointer(down2);
    tap.addPointer(down2);
88 89
    tester.closeArena(2);
    tester.route(down2);
90 91 92 93 94

    expect(didEndScale, isTrue);
    didEndScale = false;
    expect(updatedScale, isNull);
    expect(updatedFocalPoint, isNull);
95
    expect(updatedDelta, isNull);
96 97 98
    expect(didStartScale, isFalse);

    // Zoom in
99
    tester.route(pointer2.move(const Offset(0.0, 10.0)));
100 101
    expect(didStartScale, isTrue);
    didStartScale = false;
102
    expect(updatedFocalPoint, const Offset(10.0, 20.0));
103 104
    updatedFocalPoint = null;
    expect(updatedScale, 2.0);
105 106
    expect(updatedHorizontalScale, 2.0);
    expect(updatedVerticalScale, 2.0);
107
    expect(updatedDelta, const Offset(-5.0, -5.0));
108
    updatedScale = null;
109 110
    updatedHorizontalScale = null;
    updatedVerticalScale = null;
111
    updatedDelta = null;
112 113 114 115
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

    // Zoom out
116 117
    tester.route(pointer2.move(const Offset(15.0, 25.0)));
    expect(updatedFocalPoint, const Offset(17.5, 27.5));
118
    expect(updatedScale, 0.5);
119 120
    expect(updatedHorizontalScale, 0.5);
    expect(updatedVerticalScale, 0.5);
121
    expect(updatedDelta, const Offset(2.5, 2.5));
122 123
    expect(didTap, isFalse);

124 125 126 127 128 129 130 131 132
    // Horizontal scaling
    tester.route(pointer2.move(const Offset(0.0, 20.0)));
    expect(updatedHorizontalScale, 2.0);
    expect(updatedVerticalScale, 1.0);

    // Vertical scaling
    tester.route(pointer2.move(const Offset(10.0, 10.0)));
    expect(updatedHorizontalScale, 1.0);
    expect(updatedVerticalScale, 2.0);
133
    expect(updatedDelta, const Offset(0.0, -5.0));
134 135 136
    tester.route(pointer2.move(const Offset(15.0, 25.0)));
    updatedFocalPoint = null;
    updatedScale = null;
137
    updatedDelta = null;
138

139
    // Three-finger scaling
140
    final TestPointer pointer3 = TestPointer(3);
141
    final PointerDownEvent down3 = pointer3.down(const Offset(25.0, 35.0));
142 143
    scale.addPointer(down3);
    tap.addPointer(down3);
144 145
    tester.closeArena(3);
    tester.route(down3);
146 147 148 149 150

    expect(didEndScale, isTrue);
    didEndScale = false;
    expect(updatedScale, isNull);
    expect(updatedFocalPoint, isNull);
151
    expect(updatedDelta, isNull);
152 153 154
    expect(didStartScale, isFalse);

    // Zoom in
155
    tester.route(pointer3.move(const Offset(55.0, 65.0)));
156 157
    expect(didStartScale, isTrue);
    didStartScale = false;
158
    expect(updatedFocalPoint, const Offset(30.0, 40.0));
159 160 161
    updatedFocalPoint = null;
    expect(updatedScale, 5.0);
    updatedScale = null;
162 163
    expect(updatedDelta, const Offset(10.0, 10.0));
    updatedDelta = null;
164 165 166 167
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

    // Return to original positions but with different fingers
168 169 170
    tester.route(pointer1.move(const Offset(25.0, 35.0)));
    tester.route(pointer2.move(const Offset(20.0, 30.0)));
    tester.route(pointer3.move(const Offset(15.0, 25.0)));
171
    expect(didStartScale, isFalse);
172
    expect(updatedFocalPoint, const Offset(20.0, 30.0));
173 174 175
    updatedFocalPoint = null;
    expect(updatedScale, 1.0);
    updatedScale = null;
176 177
    expect(updatedDelta, Offset.zero);
    updatedDelta = null;
178 179 180
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

181
    tester.route(pointer1.up());
182 183 184
    expect(didStartScale, isFalse);
    expect(updatedFocalPoint, isNull);
    expect(updatedScale, isNull);
185
    expect(updatedDelta, isNull);
186 187 188 189 190
    expect(didEndScale, isTrue);
    didEndScale = false;
    expect(didTap, isFalse);

    // Continue scaling with two fingers
191
    tester.route(pointer3.move(const Offset(10.0, 20.0)));
192 193
    expect(didStartScale, isTrue);
    didStartScale = false;
194
    expect(updatedFocalPoint, const Offset(15.0, 25.0));
195 196 197
    updatedFocalPoint = null;
    expect(updatedScale, 2.0);
    updatedScale = null;
198 199
    expect(updatedDelta, const Offset(-2.5, -2.5));
    updatedDelta = null;
200

201 202 203 204 205 206 207 208 209 210 211
    // Continue rotating with two fingers
    tester.route(pointer3.move(const Offset(30.0, 40.0)));
    expect(updatedFocalPoint, const Offset(25.0, 35.0));
    updatedFocalPoint = null;
    expect(updatedScale, 2.0);
    updatedScale = null;
    tester.route(pointer3.move(const Offset(10.0, 20.0)));
    expect(updatedFocalPoint, const Offset(15.0, 25.0));
    updatedFocalPoint = null;
    expect(updatedScale, 2.0);
    updatedScale = null;
212 213
    expect(updatedDelta, const Offset(-2.5, -2.5));
    updatedDelta = null;
214

215
    tester.route(pointer2.up());
216 217 218
    expect(didStartScale, isFalse);
    expect(updatedFocalPoint, isNull);
    expect(updatedScale, isNull);
219
    expect(updatedDelta, isNull);
220 221 222 223 224
    expect(didEndScale, isTrue);
    didEndScale = false;
    expect(didTap, isFalse);

    // Continue panning with one finger
225
    tester.route(pointer3.move(Offset.zero));
226 227
    expect(didStartScale, isTrue);
    didStartScale = false;
228
    expect(updatedFocalPoint, Offset.zero);
229 230 231
    updatedFocalPoint = null;
    expect(updatedScale, 1.0);
    updatedScale = null;
232 233
    expect(updatedDelta, const Offset(-10.0, -20.0));
    updatedDelta = null;
234 235

    // We are done
236
    tester.route(pointer3.up());
237 238 239
    expect(didStartScale, isFalse);
    expect(updatedFocalPoint, isNull);
    expect(updatedScale, isNull);
240
    expect(updatedDelta, isNull);
241 242 243 244 245 246 247
    expect(didEndScale, isTrue);
    didEndScale = false;
    expect(didTap, isFalse);

    scale.dispose();
    tap.dispose();
  });
248

249 250 251 252 253 254 255 256
  testGesture('Rejects scale gestures from unallowed device kinds', (GestureTester tester) {
    final ScaleGestureRecognizer scale = ScaleGestureRecognizer(kind: PointerDeviceKind.touch);

    bool didStartScale = false;
    scale.onStart = (ScaleStartDetails details) {
      didStartScale = true;
    };

257
    double? updatedScale;
258 259 260 261 262 263
    scale.onUpdate = (ScaleUpdateDetails details) {
      updatedScale = details.scale;
    };

    final TestPointer mousePointer = TestPointer(1, PointerDeviceKind.mouse);

264
    final PointerDownEvent down = mousePointer.down(Offset.zero);
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
    scale.addPointer(down);
    tester.closeArena(1);

    // One-finger panning
    tester.route(down);
    expect(didStartScale, isFalse);
    expect(updatedScale, isNull);

    // Using a mouse, the scale gesture shouldn't even start.
    tester.route(mousePointer.move(const Offset(20.0, 30.0)));
    expect(didStartScale, isFalse);
    expect(updatedScale, isNull);

    scale.dispose();
  });

  testGesture('Scale gestures starting from allowed device kinds cannot be ended from unallowed devices', (GestureTester tester) {
    final ScaleGestureRecognizer scale = ScaleGestureRecognizer(kind: PointerDeviceKind.touch);

    bool didStartScale = false;
285
    Offset? updatedFocalPoint;
286 287 288 289 290
    scale.onStart = (ScaleStartDetails details) {
      didStartScale = true;
      updatedFocalPoint = details.focalPoint;
    };

291
    double? updatedScale;
292 293 294 295 296 297 298 299 300 301 302 303
    scale.onUpdate = (ScaleUpdateDetails details) {
      updatedScale = details.scale;
      updatedFocalPoint = details.focalPoint;
    };

    bool didEndScale = false;
    scale.onEnd = (ScaleEndDetails details) {
      didEndScale = true;
    };

    final TestPointer touchPointer = TestPointer(1, PointerDeviceKind.touch);

304
    final PointerDownEvent down = touchPointer.down(Offset.zero);
305 306 307 308 309 310 311 312
    scale.addPointer(down);
    tester.closeArena(1);

    // One-finger panning
    tester.route(down);
    expect(didStartScale, isTrue);
    didStartScale = false;
    expect(updatedScale, isNull);
313
    expect(updatedFocalPoint, Offset.zero);
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
    expect(didEndScale, isFalse);

    // The gesture can start using one touch finger.
    tester.route(touchPointer.move(const Offset(20.0, 30.0)));
    expect(updatedFocalPoint, const Offset(20.0, 30.0));
    updatedFocalPoint = null;
    expect(updatedScale, 1.0);
    updatedScale = null;
    expect(didEndScale, isFalse);

    // Two-finger scaling
    final TestPointer mousePointer = TestPointer(2, PointerDeviceKind.mouse);
    final PointerDownEvent down2 = mousePointer.down(const Offset(10.0, 20.0));
    scale.addPointer(down2);
    tester.closeArena(2);
    tester.route(down2);

    // Mouse-generated events are ignored.
    expect(didEndScale, isFalse);
    expect(updatedScale, isNull);
    expect(didStartScale, isFalse);

    // Zoom in using a mouse doesn't work either.
    tester.route(mousePointer.move(const Offset(0.0, 10.0)));
    expect(updatedScale, isNull);
    expect(didEndScale, isFalse);

    scale.dispose();
  });

344
  testGesture('Scale gesture competes with drag', (GestureTester tester) {
345 346
    final ScaleGestureRecognizer scale = ScaleGestureRecognizer();
    final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer();
347 348 349 350 351 352 353 354 355 356

    final List<String> log = <String>[];

    scale.onStart = (ScaleStartDetails details) { log.add('scale-start'); };
    scale.onUpdate = (ScaleUpdateDetails details) { log.add('scale-update'); };
    scale.onEnd = (ScaleEndDetails details) { log.add('scale-end'); };

    drag.onStart = (DragStartDetails details) { log.add('drag-start'); };
    drag.onEnd = (DragEndDetails details) { log.add('drag-end'); };

357
    final TestPointer pointer1 = TestPointer(1);
358

359
    final PointerDownEvent down = pointer1.down(const Offset(10.0, 10.0));
360 361 362 363 364 365 366 367 368 369
    scale.addPointer(down);
    drag.addPointer(down);

    tester.closeArena(1);
    expect(log, isEmpty);

    // Vertical moves are scales.
    tester.route(down);
    expect(log, isEmpty);

370 371 372
    // scale will win if focal point delta exceeds 18.0*2

    tester.route(pointer1.move(const Offset(10.0, 50.0))); // delta of 40.0 exceeds 18.0*2
373 374 375
    expect(log, equals(<String>['scale-start', 'scale-update']));
    log.clear();

376
    final TestPointer pointer2 = TestPointer(2);
377
    final PointerDownEvent down2 = pointer2.down(const Offset(10.0, 20.0));
378 379 380 381 382 383 384 385 386 387 388
    scale.addPointer(down2);
    drag.addPointer(down2);

    tester.closeArena(2);
    expect(log, isEmpty);

    // Second pointer joins scale even though it moves horizontally.
    tester.route(down2);
    expect(log, <String>['scale-end']);
    log.clear();

389
    tester.route(pointer2.move(const Offset(30.0, 20.0)));
390 391 392 393 394 395 396 397 398 399 400
    expect(log, equals(<String>['scale-start', 'scale-update']));
    log.clear();

    tester.route(pointer1.up());
    expect(log, equals(<String>['scale-end']));
    log.clear();

    tester.route(pointer2.up());
    expect(log, isEmpty);
    log.clear();

401 402 403 404
    // Horizontal moves are either drags or scales, depending on which wins first.
    // TODO(ianh): https://github.com/flutter/flutter/issues/11384
    // In this case, we move fast, so that the scale wins. If we moved slowly,
    // the horizontal drag would win, since it was added first.
405
    final TestPointer pointer3 = TestPointer(3);
406
    final PointerDownEvent down3 = pointer3.down(const Offset(30.0, 30.0));
407 408 409 410 411 412 413
    scale.addPointer(down3);
    drag.addPointer(down3);
    tester.closeArena(3);
    tester.route(down3);

    expect(log, isEmpty);

414
    tester.route(pointer3.move(const Offset(100.0, 30.0)));
415 416 417 418 419 420 421 422 423 424
    expect(log, equals(<String>['scale-start', 'scale-update']));
    log.clear();

    tester.route(pointer3.up());
    expect(log, equals(<String>['scale-end']));
    log.clear();

    scale.dispose();
    drag.dispose();
  });
425 426 427 428 429 430

  testGesture('Should recognize rotation gestures', (GestureTester tester) {
    final ScaleGestureRecognizer scale = ScaleGestureRecognizer();
    final TapGestureRecognizer tap = TapGestureRecognizer();

    bool didStartScale = false;
431
    Offset? updatedFocalPoint;
432 433 434 435 436
    scale.onStart = (ScaleStartDetails details) {
      didStartScale = true;
      updatedFocalPoint = details.focalPoint;
    };

437
    double? updatedRotation;
438
    Offset? updatedDelta;
439 440 441
    scale.onUpdate = (ScaleUpdateDetails details) {
      updatedRotation = details.rotation;
      updatedFocalPoint = details.focalPoint;
442
      updatedDelta = details.delta;
443 444 445 446 447 448 449 450 451 452 453 454 455 456
    };

    bool didEndScale = false;
    scale.onEnd = (ScaleEndDetails details) {
      didEndScale = true;
    };

    bool didTap = false;
    tap.onTap = () {
      didTap = true;
    };

    final TestPointer pointer1 = TestPointer(1);

457
    final PointerDownEvent down = pointer1.down(Offset.zero);
458 459 460 461 462 463 464
    scale.addPointer(down);
    tap.addPointer(down);

    tester.closeArena(1);
    expect(didStartScale, isFalse);
    expect(updatedRotation, isNull);
    expect(updatedFocalPoint, isNull);
465
    expect(updatedDelta, isNull);
466 467 468 469 470 471 472 473 474 475
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

    tester.route(down);
    tester.route(pointer1.move(const Offset(20.0, 30.0)));
    expect(didStartScale, isTrue);
    didStartScale = false;

    expect(updatedFocalPoint, const Offset(20.0, 30.0));
    updatedFocalPoint = null;
476 477
    expect(updatedDelta, const Offset(20.0, 30.0));
    updatedDelta = null;
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
    expect(updatedRotation, 0.0);
    updatedRotation = null;
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

    // Two-finger scaling
    final TestPointer pointer2 = TestPointer(2);
    final PointerDownEvent down2 = pointer2.down(const Offset(30.0, 40.0));
    scale.addPointer(down2);
    tap.addPointer(down2);
    tester.closeArena(2);
    tester.route(down2);

    expect(didEndScale, isTrue);
    didEndScale = false;
    expect(updatedFocalPoint, isNull);
494
    expect(updatedDelta, isNull);
495 496 497 498 499 500 501 502 503
    expect(updatedRotation, isNull);
    expect(didStartScale, isFalse);

    // Zoom in
    tester.route(pointer2.move(const Offset(40.0, 50.0)));
    expect(didStartScale, isTrue);
    didStartScale = false;
    expect(updatedFocalPoint, const Offset(30.0, 40.0));
    updatedFocalPoint = null;
504 505
    expect(updatedDelta, const Offset(5.0, 5.0));
    updatedDelta = null;
506 507 508 509 510 511 512 513 514
    expect(updatedRotation, 0.0);
    updatedRotation = null;
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

    // Rotation
    tester.route(pointer2.move(const Offset(0.0, 10.0)));
    expect(updatedFocalPoint, const Offset(10.0, 20.0));
    updatedFocalPoint = null;
515 516
    expect(updatedDelta, const Offset(-15.0, -15.0));
    updatedDelta = null;
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
    expect(updatedRotation, math.pi);
    updatedRotation = null;
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

    // Three-finger scaling
    final TestPointer pointer3 = TestPointer(3);
    final PointerDownEvent down3 = pointer3.down(const Offset(25.0, 35.0));
    scale.addPointer(down3);
    tap.addPointer(down3);
    tester.closeArena(3);
    tester.route(down3);

    expect(didEndScale, isTrue);
    didEndScale = false;
    expect(updatedFocalPoint, isNull);
533
    expect(updatedDelta, isNull);
534 535 536 537 538 539 540 541 542
    expect(updatedRotation, isNull);
    expect(didStartScale, isFalse);

    // Zoom in
    tester.route(pointer3.move(const Offset(55.0, 65.0)));
    expect(didStartScale, isTrue);
    didStartScale = false;
    expect(updatedFocalPoint, const Offset(25.0, 35.0));
    updatedFocalPoint = null;
543 544
    expect(updatedDelta, const Offset(10.0, 10.0));
    updatedDelta = null;
545 546 547 548 549 550 551 552 553 554 555 556
    expect(updatedRotation, 0.0);
    updatedRotation = null;
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

    // Return to original positions but with different fingers
    tester.route(pointer1.move(const Offset(25.0, 35.0)));
    tester.route(pointer2.move(const Offset(20.0, 30.0)));
    tester.route(pointer3.move(const Offset(15.0, 25.0)));
    expect(didStartScale, isFalse);
    expect(updatedFocalPoint, const Offset(20.0, 30.0));
    updatedFocalPoint = null;
557 558
    expect(updatedDelta, const Offset(5.0, 5.0));
    updatedDelta = null;
559 560 561 562 563 564 565 566
    expect(updatedRotation, 0.0);
    updatedRotation = null;
    expect(didEndScale, isFalse);
    expect(didTap, isFalse);

    tester.route(pointer1.up());
    expect(didStartScale, isFalse);
    expect(updatedFocalPoint, isNull);
567
    expect(updatedDelta, isNull);
568 569 570 571 572 573 574 575 576 577 578
    expect(updatedRotation, isNull);
    expect(didEndScale, isTrue);
    didEndScale = false;
    expect(didTap, isFalse);

    // Continue scaling with two fingers
    tester.route(pointer3.move(const Offset(10.0, 20.0)));
    expect(didStartScale, isTrue);
    didStartScale = false;
    expect(updatedFocalPoint, const Offset(15.0, 25.0));
    updatedFocalPoint = null;
579 580
    expect(updatedDelta, const Offset(-2.5, -2.5));
    updatedDelta = null;
581 582 583 584 585 586 587
    expect(updatedRotation, 0.0);
    updatedRotation = null;

    // Continue rotating with two fingers
    tester.route(pointer3.move(const Offset(30.0, 40.0)));
    expect(updatedFocalPoint, const Offset(25.0, 35.0));
    updatedFocalPoint = null;
588 589
    expect(updatedDelta, const Offset(7.5, 7.5));
    updatedDelta = null;
590 591 592 593 594
    expect(updatedRotation, - math.pi);
    updatedRotation = null;
    tester.route(pointer3.move(const Offset(10.0, 20.0)));
    expect(updatedFocalPoint, const Offset(15.0, 25.0));
    updatedFocalPoint = null;
595 596
    expect(updatedDelta, const Offset(-2.5, -2.5));
    updatedDelta = null;
597 598 599 600 601 602
    expect(updatedRotation, 0.0);
    updatedRotation = null;

    tester.route(pointer2.up());
    expect(didStartScale, isFalse);
    expect(updatedFocalPoint, isNull);
603
    expect(updatedDelta, isNull);
604 605 606 607 608 609 610 611 612
    expect(updatedRotation, isNull);
    expect(didEndScale, isTrue);
    didEndScale = false;
    expect(didTap, isFalse);

    // We are done
    tester.route(pointer3.up());
    expect(didStartScale, isFalse);
    expect(updatedFocalPoint, isNull);
613
    expect(updatedDelta, isNull);
614 615 616 617 618 619 620 621
    expect(updatedRotation, isNull);
    expect(didEndScale, isFalse);
    didEndScale = false;
    expect(didTap, isFalse);

    scale.dispose();
    tap.dispose();
  });
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
  // Regressing test for https://github.com/flutter/flutter/issues/78941
  testGesture('First rotation test', (GestureTester tester) {
    final ScaleGestureRecognizer scale = ScaleGestureRecognizer();

    double? updatedRotation;
    scale.onUpdate = (ScaleUpdateDetails details) {
      updatedRotation = details.rotation;
    };

    final TestPointer pointer1 = TestPointer(1);
    final PointerDownEvent down = pointer1.down(Offset.zero);
    scale.addPointer(down);
    tester.closeArena(1);
    tester.route(down);

    final TestPointer pointer2 = TestPointer(2);
    final PointerDownEvent down2 = pointer2.down(const Offset(10.0, 10.0));
    scale.addPointer(down2);
    tester.closeArena(2);
    tester.route(down2);

    expect(updatedRotation, isNull);

    // Rotation 45°.
    tester.route(pointer2.move(const Offset(0.0, 10.0)));
    expect(updatedRotation, math.pi / 4.0);
  });

651 652 653 654 655 656 657 658 659 660 661 662 663
  testGesture('Scale gestures pointer count test', (GestureTester tester) {
    final ScaleGestureRecognizer scale = ScaleGestureRecognizer();

    int pointerCountOfStart = 0;
    scale.onStart = (ScaleStartDetails details) => pointerCountOfStart = details.pointerCount;

    int pointerCountOfUpdate = 0;
    scale.onUpdate = (ScaleUpdateDetails details) => pointerCountOfUpdate = details.pointerCount;

    int pointerCountOfEnd = 0;
    scale.onEnd = (ScaleEndDetails details) => pointerCountOfEnd = details.pointerCount;

    final TestPointer pointer1 = TestPointer(1);
664
    final PointerDownEvent down = pointer1.down(Offset.zero);
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
    scale.addPointer(down);
    tester.closeArena(1);

    // One-finger panning
    tester.route(down);
    // One pointer in contact with the screen now.
    expect(pointerCountOfStart, 1);
    tester.route(pointer1.move(const Offset(20.0, 30.0)));
    expect(pointerCountOfUpdate, 1);

    // Two-finger scaling
    final TestPointer pointer2 = TestPointer(2);
    final PointerDownEvent down2 = pointer2.down(const Offset(10.0, 20.0));
    scale.addPointer(down2);
    tester.closeArena(2);
    tester.route(down2);
    // Two pointers in contact with the screen now.
    expect(pointerCountOfEnd, 2); // Additional pointer down will trigger an end event.

    tester.route(pointer2.move(const Offset(0.0, 10.0)));
    expect(pointerCountOfStart, 2); // The new pointer move will trigger a start event.
    expect(pointerCountOfUpdate, 2);

    tester.route(pointer1.up());
    // One pointer in contact with the screen now.
    expect(pointerCountOfEnd, 1);

    tester.route(pointer2.move(const Offset(0.0, 10.0)));
    expect(pointerCountOfStart, 1);
    expect(pointerCountOfUpdate, 1);

    tester.route(pointer2.up());
    // No pointer in contact with the screen now.
    expect(pointerCountOfEnd, 0);

    scale.dispose();
  });
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716

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