gesture_detector_semantics_test.dart 25.9 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
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 7
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
8
import 'package:flutter_test/flutter_test.dart';
9 10 11 12 13

import 'semantics_tester.dart';

void main() {
  testWidgets('Vertical gesture detector has up/down actions', (WidgetTester tester) async {
14
    final SemanticsTester semantics = SemanticsTester(tester);
15 16

    int callCount = 0;
17
    final GlobalKey detectorKey = GlobalKey();
18 19

    await tester.pumpWidget(
20 21
      Center(
        child: GestureDetector(
22 23 24 25
          key: detectorKey,
          onVerticalDragStart: (DragStartDetails _) {
            callCount += 1;
          },
26
          child: Container(),
27
        ),
28
      ),
29 30 31
    );

    expect(semantics, includesNodeWith(
32 33
      actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
    ));
34

35 36 37
    final int detectorId = detectorKey.currentContext!.findRenderObject()!.debugSemantics!.id;
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollLeft);
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollRight);
38
    expect(callCount, 0);
39
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollUp);
40
    expect(callCount, 1);
41
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollDown);
42
    expect(callCount, 2);
43 44

    semantics.dispose();
45 46 47
  });

  testWidgets('Horizontal gesture detector has up/down actions', (WidgetTester tester) async {
48
    final SemanticsTester semantics = SemanticsTester(tester);
49 50

    int callCount = 0;
51
    final GlobalKey detectorKey = GlobalKey();
52 53

    await tester.pumpWidget(
54 55
        Center(
          child: GestureDetector(
56 57 58 59
            key: detectorKey,
            onHorizontalDragStart: (DragStartDetails _) {
              callCount += 1;
            },
60
            child: Container(),
61
          ),
62
        ),
63 64 65
    );

    expect(semantics, includesNodeWith(
66 67
      actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight],
    ));
68

69 70 71
    final int detectorId = detectorKey.currentContext!.findRenderObject()!.debugSemantics!.id;
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollUp);
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollDown);
72
    expect(callCount, 0);
73
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollLeft);
74
    expect(callCount, 1);
75
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollRight);
76
    expect(callCount, 2);
77 78

    semantics.dispose();
79
  });
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

  testWidgets('All registered handlers for the gesture kind are called', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);

    final Set<String> logs = <String>{};
    final GlobalKey detectorKey = GlobalKey();

    await tester.pumpWidget(
      Center(
        child: GestureDetector(
          key: detectorKey,
          onHorizontalDragStart: (_) { logs.add('horizontal'); },
          onPanStart: (_) { logs.add('pan'); },
          child: Container(),
        ),
      ),
    );

98 99
    final int detectorId = detectorKey.currentContext!.findRenderObject()!.debugSemantics!.id;
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollLeft);
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
    expect(logs, <String>{'horizontal', 'pan'});

    semantics.dispose();
  });

  testWidgets('Replacing recognizers should update semantic handlers', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);

    // How the test is set up:
    //
    //  * In the base state, RawGestureDetector's recognizer is a HorizontalGR
    //  * Calling `introduceLayoutPerformer()` adds a `_TestLayoutPerformer` as
    //    child of RawGestureDetector, which invokes a given callback during
    //    layout phase.
    //  * The aforementioned callback replaces the detector's recognizer with a
    //    TapGR.
    //  * This test makes sure the replacement correctly updates semantics.

    final Set<String> logs = <String>{};
    final GlobalKey<RawGestureDetectorState> detectorKey = GlobalKey();
120
    void performLayout() {
121
      detectorKey.currentState!.replaceGestureRecognizers(<Type, GestureRecognizerFactory>{
122 123 124
        TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
          () => TapGestureRecognizer(),
          (TapGestureRecognizer instance) {
125
            instance.onTap = () { logs.add('tap'); };
126
          },
127
        ),
128
      });
129
    }
130 131

    bool hasLayoutPerformer = false;
132
    late VoidCallback introduceLayoutPerformer;
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
    await tester.pumpWidget(
      StatefulBuilder(
        builder: (BuildContext context, StateSetter setter) {
          introduceLayoutPerformer = () {
            setter(() {
              hasLayoutPerformer = true;
            });
          };
          return Center(
            child: RawGestureDetector(
              key: detectorKey,
              gestures: <Type, GestureRecognizerFactory>{
                HorizontalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
                  () => HorizontalDragGestureRecognizer(),
                  (HorizontalDragGestureRecognizer instance) {
148
                    instance.onStart = (_) { logs.add('horizontal'); };
149
                  },
150
                ),
151 152 153 154 155 156 157 158
              },
              child: hasLayoutPerformer ? _TestLayoutPerformer(performLayout: performLayout) : null,
            ),
          );
        },
      ),
    );

159 160
    final int detectorId = detectorKey.currentContext!.findRenderObject()!.debugSemantics!.id;
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollLeft);
161 162 163 164 165 166
    expect(logs, <String>{'horizontal'});
    logs.clear();

    introduceLayoutPerformer();
    await tester.pumpAndSettle();

167 168
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollLeft);
    tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.tap);
169 170 171 172 173 174
    expect(logs, <String>{'tap'});
    logs.clear();

    semantics.dispose();
  });

175
  group("RawGestureDetector's custom semantics delegate", () {
176 177 178 179 180 181 182 183 184 185 186
    testWidgets('should update semantics notations when switching from the default delegate', (WidgetTester tester) async {
      final SemanticsTester semantics = SemanticsTester(tester);
      final Map<Type, GestureRecognizerFactory> gestures =
        _buildGestureMap(() => LongPressGestureRecognizer(), null)
        ..addAll( _buildGestureMap(() => TapGestureRecognizer(), null));
      await tester.pumpWidget(
        Center(
          child: RawGestureDetector(
            gestures: gestures,
            child: Container(),
          ),
187
        ),
188 189 190
      );

      expect(semantics, includesNodeWith(
191 192
        actions: <SemanticsAction>[SemanticsAction.longPress, SemanticsAction.tap],
      ));
193 194 195 196 197 198 199 200

      await tester.pumpWidget(
        Center(
          child: RawGestureDetector(
            gestures: gestures,
            semantics: _TestSemanticsGestureDelegate(onTap: () {}),
            child: Container(),
          ),
201
        ),
202 203 204
      );

      expect(semantics, includesNodeWith(
205 206
        actions: <SemanticsAction>[SemanticsAction.tap],
      ));
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222

      semantics.dispose();
    });

    testWidgets('should update semantics notations when switching to the default delegate', (WidgetTester tester) async {
      final SemanticsTester semantics = SemanticsTester(tester);
      final Map<Type, GestureRecognizerFactory> gestures =
        _buildGestureMap(() => LongPressGestureRecognizer(), null)
        ..addAll( _buildGestureMap(() => TapGestureRecognizer(), null));
      await tester.pumpWidget(
        Center(
          child: RawGestureDetector(
            gestures: gestures,
            semantics: _TestSemanticsGestureDelegate(onTap: () {}),
            child: Container(),
          ),
223
        ),
224 225 226
      );

      expect(semantics, includesNodeWith(
227 228
        actions: <SemanticsAction>[SemanticsAction.tap],
      ));
229 230 231 232 233 234 235

      await tester.pumpWidget(
        Center(
          child: RawGestureDetector(
            gestures: gestures,
            child: Container(),
          ),
236
        ),
237 238 239
      );

      expect(semantics, includesNodeWith(
240 241
        actions: <SemanticsAction>[SemanticsAction.longPress, SemanticsAction.tap],
      ));
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

      semantics.dispose();
    });

    testWidgets('should update semantics notations when switching from a different custom delegate', (WidgetTester tester) async {
      final SemanticsTester semantics = SemanticsTester(tester);
      final Map<Type, GestureRecognizerFactory> gestures =
        _buildGestureMap(() => LongPressGestureRecognizer(), null)
        ..addAll( _buildGestureMap(() => TapGestureRecognizer(), null));
      await tester.pumpWidget(
        Center(
          child: RawGestureDetector(
            gestures: gestures,
            semantics: _TestSemanticsGestureDelegate(onTap: () {}),
            child: Container(),
          ),
258
        ),
259 260 261
      );

      expect(semantics, includesNodeWith(
262 263
        actions: <SemanticsAction>[SemanticsAction.tap],
      ));
264 265 266 267 268 269 270 271

      await tester.pumpWidget(
        Center(
          child: RawGestureDetector(
            gestures: gestures,
            semantics: _TestSemanticsGestureDelegate(onLongPress: () {}),
            child: Container(),
          ),
272
        ),
273 274 275
      );

      expect(semantics, includesNodeWith(
276 277
        actions: <SemanticsAction>[SemanticsAction.longPress],
      ));
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297

      semantics.dispose();
    });

    testWidgets('should correctly call callbacks', (WidgetTester tester) async {
      final SemanticsTester semantics = SemanticsTester(tester);
      final List<String> logs = <String>[];
      final GlobalKey<RawGestureDetectorState> detectorKey = GlobalKey();
      await tester.pumpWidget(
        Center(
          child: RawGestureDetector(
            key: detectorKey,
            semantics: _TestSemanticsGestureDelegate(
              onTap: () { logs.add('tap'); },
              onLongPress: () { logs.add('longPress'); },
              onHorizontalDragUpdate: (_) { logs.add('horizontal'); },
              onVerticalDragUpdate: (_) { logs.add('vertical'); },
            ),
            child: Container(),
          ),
298
        ),
299 300
      );

301 302
      final int detectorId = detectorKey.currentContext!.findRenderObject()!.debugSemantics!.id;
      tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.tap);
303 304 305
      expect(logs, <String>['tap']);
      logs.clear();

306
      tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.longPress);
307 308 309
      expect(logs, <String>['longPress']);
      logs.clear();

310
      tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollLeft);
311 312 313
      expect(logs, <String>['horizontal']);
      logs.clear();

314
      tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollUp);
315 316 317 318 319 320 321
      expect(logs, <String>['vertical']);
      logs.clear();

      semantics.dispose();
    });
  });

322
  group("RawGestureDetector's default semantics delegate", () {
323 324 325 326 327 328 329 330 331
    group('should map onTap to', () {
      testWidgets('null when there is no TapGR', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              gestures: _buildGestureMap(null, null),
              child: Container(),
            ),
332
          ),
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
        );

        expect(semantics, isNot(includesNodeWith(
          actions: <SemanticsAction>[SemanticsAction.tap],
        )));

        semantics.dispose();
      });

      testWidgets('non-null when there is TapGR with no callbacks', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              gestures: _buildGestureMap(
                () => TapGestureRecognizer(),
                null,
              ),
              child: Container(),
            ),
353
          ),
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
        );

        expect(semantics, includesNodeWith(
          actions: <SemanticsAction>[SemanticsAction.tap],
        ));

        semantics.dispose();
      });

      testWidgets('a callback that correctly calls callbacks', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        final GlobalKey detectorKey = GlobalKey();
        final List<String> logs = <String>[];
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              key: detectorKey,
              gestures: _buildGestureMap(
                () => TapGestureRecognizer(),
                (TapGestureRecognizer tap) {
                  tap
                    ..onTap = () {logs.add('tap');}
                    ..onTapUp = (_) {logs.add('tapUp');}
                    ..onTapDown = (_) {logs.add('tapDown');}
                    ..onTapCancel = () {logs.add('WRONG');}
379 380
                    ..onSecondaryTapDown = (_) {logs.add('WRONG');}
                    ..onTertiaryTapDown = (_) {logs.add('WRONG');};
381
                },
382 383 384
              ),
              child: Container(),
            ),
385
          ),
386 387
        );

388 389
        final int detectorId = detectorKey.currentContext!.findRenderObject()!.debugSemantics!.id;
        tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.tap);
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
        expect(logs, <String>['tapDown', 'tapUp', 'tap']);

        semantics.dispose();
      });
    });

    group('should map onLongPress to', () {
      testWidgets('null when there is no LongPressGR ', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              gestures: _buildGestureMap(null, null),
              child: Container(),
            ),
405
          ),
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
        );

        expect(semantics, isNot(includesNodeWith(
          actions: <SemanticsAction>[SemanticsAction.longPress],
        )));

        semantics.dispose();
      });

      testWidgets('non-null when there is LongPressGR with no callbacks', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              gestures: _buildGestureMap(
                () => LongPressGestureRecognizer(),
                null,
              ),
              child: Container(),
            ),
426
          ),
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
        );

        expect(semantics, includesNodeWith(
          actions: <SemanticsAction>[SemanticsAction.longPress],
        ));

        semantics.dispose();
      });

      testWidgets('a callback that correctly calls callbacks', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        final GlobalKey detectorKey = GlobalKey();
        final List<String> logs = <String>[];
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              key: detectorKey,
              gestures: _buildGestureMap(
                () => LongPressGestureRecognizer(),
                (LongPressGestureRecognizer longPress) {
                  longPress
                    ..onLongPress = () {logs.add('LP');}
                    ..onLongPressStart = (_) {logs.add('LPStart');}
                    ..onLongPressUp = () {logs.add('LPUp');}
                    ..onLongPressEnd = (_) {logs.add('LPEnd');}
                    ..onLongPressMoveUpdate = (_) {logs.add('WRONG');};
453
                },
454 455 456
              ),
              child: Container(),
            ),
457
          ),
458 459
        );

460 461
        final int detectorId = detectorKey.currentContext!.findRenderObject()!.debugSemantics!.id;
        tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.longPress);
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
        expect(logs, <String>['LPStart', 'LP', 'LPEnd', 'LPUp']);

        semantics.dispose();
      });
    });

    group('should map onHorizontalDragUpdate to', () {
      testWidgets('null when there is no matching recognizers ', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              gestures: _buildGestureMap(null, null),
              child: Container(),
            ),
477
          ),
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
        );

        expect(semantics, isNot(includesNodeWith(
          actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight],
        )));

        semantics.dispose();
      });

      testWidgets('non-null when there is either matching recognizer with no callbacks', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              gestures: _buildGestureMap(
                () => HorizontalDragGestureRecognizer(),
                null,
              ),
              child: Container(),
            ),
498
          ),
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
        );

        expect(semantics, includesNodeWith(
          actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight],
        ));

        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              gestures: _buildGestureMap(
                () => PanGestureRecognizer(),
                null,
              ),
              child: Container(),
            ),
514
          ),
515 516 517
        );

        expect(semantics, includesNodeWith(
518 519 520 521 522 523
          actions: <SemanticsAction>[
            SemanticsAction.scrollLeft,
            SemanticsAction.scrollRight,
            SemanticsAction.scrollDown,
            SemanticsAction.scrollUp,
          ],
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
        ));

        semantics.dispose();
      });

      testWidgets('a callback that correctly calls callbacks', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        final GlobalKey detectorKey = GlobalKey();
        final List<String> logs = <String>[];
        final Map<Type, GestureRecognizerFactory> gestures = _buildGestureMap(
          () => HorizontalDragGestureRecognizer(),
          (HorizontalDragGestureRecognizer horizontal) {
            horizontal
              ..onStart = (_) {logs.add('HStart');}
              ..onDown = (_) {logs.add('HDown');}
              ..onEnd = (_) {logs.add('HEnd');}
              ..onUpdate = (_) {logs.add('HUpdate');}
              ..onCancel = () {logs.add('WRONG');};
542
          },
543 544 545 546 547 548 549 550 551
        )..addAll(_buildGestureMap(
          () => PanGestureRecognizer(),
          (PanGestureRecognizer pan) {
            pan
              ..onStart = (_) {logs.add('PStart');}
              ..onDown = (_) {logs.add('PDown');}
              ..onEnd = (_) {logs.add('PEnd');}
              ..onUpdate = (_) {logs.add('PUpdate');}
              ..onCancel = () {logs.add('WRONG');};
552
          },
553 554 555 556 557 558 559 560
        ));
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              key: detectorKey,
              gestures: gestures,
              child: Container(),
            ),
561
          ),
562 563
        );

564 565
        final int detectorId = detectorKey.currentContext!.findRenderObject()!.debugSemantics!.id;
        tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollLeft);
566 567 568 569
        expect(logs, <String>['HDown', 'HStart', 'HUpdate', 'HEnd',
          'PDown', 'PStart', 'PUpdate', 'PEnd',]);
        logs.clear();

570
        tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollLeft);
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
        expect(logs, <String>['HDown', 'HStart', 'HUpdate', 'HEnd',
          'PDown', 'PStart', 'PUpdate', 'PEnd',]);

        semantics.dispose();
      });
    });

    group('should map onVerticalDragUpdate to', () {
      testWidgets('null when there is no matching recognizers ', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              gestures: _buildGestureMap(null, null),
              child: Container(),
            ),
587
          ),
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
        );

        expect(semantics, isNot(includesNodeWith(
          actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
        )));

        semantics.dispose();
      });

      testWidgets('non-null when there is either matching recognizer with no callbacks', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              gestures: _buildGestureMap(
                () => VerticalDragGestureRecognizer(),
                null,
              ),
              child: Container(),
            ),
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
        );

        expect(semantics, includesNodeWith(
          actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
        ));

        // Pan has bene tested in Horizontal

        semantics.dispose();
      });

      testWidgets('a callback that correctly calls callbacks', (WidgetTester tester) async {
        final SemanticsTester semantics = SemanticsTester(tester);
        final GlobalKey detectorKey = GlobalKey();
        final List<String> logs = <String>[];
        final Map<Type, GestureRecognizerFactory> gestures = _buildGestureMap(
          () => VerticalDragGestureRecognizer(),
          (VerticalDragGestureRecognizer horizontal) {
            horizontal
              ..onStart = (_) {logs.add('VStart');}
              ..onDown = (_) {logs.add('VDown');}
              ..onEnd = (_) {logs.add('VEnd');}
              ..onUpdate = (_) {logs.add('VUpdate');}
              ..onCancel = () {logs.add('WRONG');};
633
          },
634 635 636 637 638 639 640 641 642
        )..addAll(_buildGestureMap(
          () => PanGestureRecognizer(),
          (PanGestureRecognizer pan) {
            pan
              ..onStart = (_) {logs.add('PStart');}
              ..onDown = (_) {logs.add('PDown');}
              ..onEnd = (_) {logs.add('PEnd');}
              ..onUpdate = (_) {logs.add('PUpdate');}
              ..onCancel = () {logs.add('WRONG');};
643
          },
644 645 646 647 648 649 650 651
        ));
        await tester.pumpWidget(
          Center(
            child: RawGestureDetector(
              key: detectorKey,
              gestures: gestures,
              child: Container(),
            ),
652
          ),
653 654
        );

655 656
        final int detectorId = detectorKey.currentContext!.findRenderObject()!.debugSemantics!.id;
        tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollUp);
657 658 659 660
        expect(logs, <String>['VDown', 'VStart', 'VUpdate', 'VEnd',
          'PDown', 'PStart', 'PUpdate', 'PEnd',]);
        logs.clear();

661
        tester.binding.pipelineOwner.semanticsOwner!.performAction(detectorId, SemanticsAction.scrollDown);
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676
        expect(logs, <String>['VDown', 'VStart', 'VUpdate', 'VEnd',
          'PDown', 'PStart', 'PUpdate', 'PEnd',]);

        semantics.dispose();
      });
    });

    testWidgets('should update semantics notations when receiving new gestures', (WidgetTester tester) async {
      final SemanticsTester semantics = SemanticsTester(tester);
      await tester.pumpWidget(
        Center(
          child: RawGestureDetector(
            gestures: _buildGestureMap(() => LongPressGestureRecognizer(), null),
            child: Container(),
          ),
677
        ),
678 679 680
      );

      expect(semantics, includesNodeWith(
681 682
        actions: <SemanticsAction>[SemanticsAction.longPress],
      ));
683 684 685 686 687 688 689

      await tester.pumpWidget(
        Center(
          child: RawGestureDetector(
            gestures: _buildGestureMap(() => TapGestureRecognizer(), null),
            child: Container(),
          ),
690
        ),
691 692 693
      );

      expect(semantics, includesNodeWith(
694 695
        actions: <SemanticsAction>[SemanticsAction.tap],
      ));
696 697 698 699 700 701 702 703

      semantics.dispose();
    });
  });
}

class _TestLayoutPerformer extends SingleChildRenderObjectWidget {
  const _TestLayoutPerformer({
704
    required this.performLayout,
705
  });
706 707 708 709 710 711 712 713 714 715

  final VoidCallback performLayout;

  @override
  _RenderTestLayoutPerformer createRenderObject(BuildContext context) {
    return _RenderTestLayoutPerformer(performLayout: performLayout);
  }
}

class _RenderTestLayoutPerformer extends RenderBox {
716
  _RenderTestLayoutPerformer({required VoidCallback performLayout}) : _performLayout = performLayout;
717

718
  final VoidCallback _performLayout;
719

720 721 722 723 724
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    return const Size(1, 1);
  }

725 726 727
  @override
  void performLayout() {
    size = const Size(1, 1);
728
    _performLayout();
729 730 731 732
  }
}

Map<Type, GestureRecognizerFactory> _buildGestureMap<T extends GestureRecognizer>(
733 734
  GestureRecognizerFactoryConstructor<T>? constructor,
  GestureRecognizerFactoryInitializer<T>? initializer,
735
) {
736
  if (constructor == null) {
737
    return <Type, GestureRecognizerFactory>{};
738
  }
739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
  return <Type, GestureRecognizerFactory>{
    T: GestureRecognizerFactoryWithHandlers<T>(
      constructor,
      initializer ?? (T o) {},
    ),
  };
}

class _TestSemanticsGestureDelegate extends SemanticsGestureDelegate {
  const _TestSemanticsGestureDelegate({
    this.onTap,
    this.onLongPress,
    this.onHorizontalDragUpdate,
    this.onVerticalDragUpdate,
  });

755 756 757 758
  final GestureTapCallback? onTap;
  final GestureLongPressCallback? onLongPress;
  final GestureDragUpdateCallback? onHorizontalDragUpdate;
  final GestureDragUpdateCallback? onVerticalDragUpdate;
759 760 761 762 763 764 765 766 767

  @override
  void assignSemantics(RenderSemanticsGestureHandler renderObject) {
    renderObject
      ..onTap = onTap
      ..onLongPress = onLongPress
      ..onHorizontalDragUpdate = onHorizontalDragUpdate
      ..onVerticalDragUpdate = onVerticalDragUpdate;
  }
768
}