semantics_test.dart 44 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 6 7
import 'dart:math';
import 'dart:ui';

8
import 'package:flutter/material.dart';
9
import 'package:flutter/rendering.dart';
10 11 12
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

13
import 'semantics_tester.dart';
14 15

void main() {
16 17 18 19
  setUp(() {
    debugResetSemanticsIdCounter();
  });

20
  testWidgets('Semantics shutdown and restart', (WidgetTester tester) async {
21
    SemanticsTester? semantics = SemanticsTester(tester);
22

23
    final TestSemantics expectedSemantics = TestSemantics.root(
24
      children: <TestSemantics>[
25
        TestSemantics.rootChild(
26 27
          label: 'test1',
          textDirection: TextDirection.ltr,
28
        ),
29
      ],
30
    );
31 32

    await tester.pumpWidget(
33 34 35 36
      Semantics(
        label: 'test1',
        textDirection: TextDirection.ltr,
        child: Container(),
37
      ),
38 39
    );

40 41 42 43 44 45
    expect(semantics, hasSemantics(
      expectedSemantics,
      ignoreTransform: true,
      ignoreRect: true,
      ignoreId: true,
    ));
46

47 48
    semantics.dispose();
    semantics = null;
49 50

    expect(tester.binding.hasScheduledFrame, isFalse);
51
    semantics = SemanticsTester(tester);
52 53 54
    expect(tester.binding.hasScheduledFrame, isTrue);
    await tester.pump();

55 56 57 58 59 60
    expect(semantics, hasSemantics(
      expectedSemantics,
      ignoreTransform: true,
      ignoreRect: true,
      ignoreId: true,
    ));
61
    semantics.dispose();
62
  }, semanticsEnabled: false);
63 64

  testWidgets('Detach and reattach assert', (WidgetTester tester) async {
65 66
    final SemanticsTester semantics = SemanticsTester(tester);
    final GlobalKey key = GlobalKey();
67

68
    await tester.pumpWidget(Directionality(
Ian Hickson's avatar
Ian Hickson committed
69
      textDirection: TextDirection.ltr,
70 71
      child: Semantics(
        label: 'test1',
72
        child: Semantics(
73 74 75 76
          key: key,
          container: true,
          label: 'test2a',
          child: Container(),
77 78
        ),
      ),
Ian Hickson's avatar
Ian Hickson committed
79
    ));
80

81
    expect(semantics, hasSemantics(
82
      TestSemantics.root(
83
        children: <TestSemantics>[
84
          TestSemantics.rootChild(
85 86
            label: 'test1',
            children: <TestSemantics>[
87
              TestSemantics(
88
                label: 'test2a',
89 90 91
              ),
            ],
          ),
92
        ],
93 94 95 96
      ),
      ignoreId: true,
      ignoreRect: true,
      ignoreTransform: true,
97
    ));
98

99
    await tester.pumpWidget(Directionality(
Ian Hickson's avatar
Ian Hickson committed
100
      textDirection: TextDirection.ltr,
101 102
      child: Semantics(
        label: 'test1',
103
        child: Semantics(
104 105
          container: true,
          label: 'middle',
106
          child: Semantics(
107
            key: key,
108
            container: true,
109 110
            label: 'test2b',
            child: Container(),
111 112 113
          ),
        ),
      ),
Ian Hickson's avatar
Ian Hickson committed
114
    ));
115

116
    expect(semantics, hasSemantics(
117
      TestSemantics.root(
118
        children: <TestSemantics>[
119
          TestSemantics.rootChild(
120 121
            label: 'test1',
            children: <TestSemantics>[
122
              TestSemantics(
123
                label: 'middle',
124
                children: <TestSemantics>[
125
                  TestSemantics(
126 127 128
                    label: 'test2b',
                  ),
                ],
129 130 131
              ),
            ],
          ),
132
        ],
133 134 135 136
      ),
      ignoreId: true,
      ignoreRect: true,
      ignoreTransform: true,
137
    ));
138

139
    semantics.dispose();
140
  });
Ian Hickson's avatar
Ian Hickson committed
141 142

  testWidgets('Semantics and Directionality - RTL', (WidgetTester tester) async {
143
    final SemanticsTester semantics = SemanticsTester(tester);
Ian Hickson's avatar
Ian Hickson committed
144 145

    await tester.pumpWidget(
146
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
147
        textDirection: TextDirection.rtl,
148
        child: Semantics(
Ian Hickson's avatar
Ian Hickson committed
149
          label: 'test1',
150
          child: Container(),
Ian Hickson's avatar
Ian Hickson committed
151 152 153 154
        ),
      ),
    );

155
    expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.rtl));
156
    semantics.dispose();
Ian Hickson's avatar
Ian Hickson committed
157 158 159
  });

  testWidgets('Semantics and Directionality - LTR', (WidgetTester tester) async {
160
    final SemanticsTester semantics = SemanticsTester(tester);
Ian Hickson's avatar
Ian Hickson committed
161 162

    await tester.pumpWidget(
163
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
164
        textDirection: TextDirection.ltr,
165
        child: Semantics(
Ian Hickson's avatar
Ian Hickson committed
166
          label: 'test1',
167
          child: Container(),
Ian Hickson's avatar
Ian Hickson committed
168 169 170 171
        ),
      ),
    );

172
    expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.ltr));
173
    semantics.dispose();
Ian Hickson's avatar
Ian Hickson committed
174 175
  });

176
  testWidgets('Semantics and Directionality - cannot override RTL with LTR', (WidgetTester tester) async {
177
    final SemanticsTester semantics = SemanticsTester(tester);
Ian Hickson's avatar
Ian Hickson committed
178

179
    final TestSemantics expectedSemantics = TestSemantics.root(
180
      children: <TestSemantics>[
181
        TestSemantics.rootChild(
182 183
          label: 'test1',
          textDirection: TextDirection.ltr,
184
        ),
185
      ],
Ian Hickson's avatar
Ian Hickson committed
186 187 188
    );

    await tester.pumpWidget(
189
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
190
        textDirection: TextDirection.rtl,
191
        child: Semantics(
Ian Hickson's avatar
Ian Hickson committed
192 193
          label: 'test1',
          textDirection: TextDirection.ltr,
194
          child: Container(),
Ian Hickson's avatar
Ian Hickson committed
195 196 197 198
        ),
      ),
    );

199
    expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
200
    semantics.dispose();
Ian Hickson's avatar
Ian Hickson committed
201 202
  });

203
  testWidgets('Semantics and Directionality - cannot override LTR with RTL', (WidgetTester tester) async {
204
    final SemanticsTester semantics = SemanticsTester(tester);
Ian Hickson's avatar
Ian Hickson committed
205

206
    final TestSemantics expectedSemantics = TestSemantics.root(
207
      children: <TestSemantics>[
208
        TestSemantics.rootChild(
209 210
          label: 'test1',
          textDirection: TextDirection.rtl,
211
        ),
212
      ],
Ian Hickson's avatar
Ian Hickson committed
213 214 215
    );

    await tester.pumpWidget(
216
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
217
        textDirection: TextDirection.ltr,
218
        child: Semantics(
Ian Hickson's avatar
Ian Hickson committed
219 220
          label: 'test1',
          textDirection: TextDirection.rtl,
221
          child: Container(),
Ian Hickson's avatar
Ian Hickson committed
222 223 224 225
        ),
      ),
    );

226
    expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
227
    semantics.dispose();
Ian Hickson's avatar
Ian Hickson committed
228
  });
229 230

  testWidgets('Semantics label and hint', (WidgetTester tester) async {
231
    final SemanticsTester semantics = SemanticsTester(tester);
232 233

    await tester.pumpWidget(
234
      Directionality(
235
        textDirection: TextDirection.ltr,
236
        child: Semantics(
237 238 239
          label: 'label',
          hint: 'hint',
          value: 'value',
240
          child: Container(),
241 242 243 244
        ),
      ),
    );

245
    final TestSemantics expectedSemantics = TestSemantics.root(
246
      children: <TestSemantics>[
247
        TestSemantics.rootChild(
248 249 250 251
          label: 'label',
          hint: 'hint',
          value: 'value',
          textDirection: TextDirection.ltr,
252
        ),
253
      ],
254 255 256
    );

    expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
257
    semantics.dispose();
258 259 260
  });

  testWidgets('Semantics hints can merge', (WidgetTester tester) async {
261
    final SemanticsTester semantics = SemanticsTester(tester);
262 263

    await tester.pumpWidget(
264
      Directionality(
265
        textDirection: TextDirection.ltr,
266
        child: Semantics(
267
          container: true,
268
          child: Column(
269
            children: <Widget>[
270
              Semantics(
271 272
                hint: 'hint one',
              ),
273
              Semantics(
274
                hint: 'hint two',
275
              ),
276 277 278 279 280 281 282

            ],
          ),
        ),
      ),
    );

283
    final TestSemantics expectedSemantics = TestSemantics.root(
284
      children: <TestSemantics>[
285
        TestSemantics.rootChild(
286 287
          hint: 'hint one\nhint two',
          textDirection: TextDirection.ltr,
288
        ),
289
      ],
290 291 292
    );

    expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
293
    semantics.dispose();
294 295 296
  });

  testWidgets('Semantics values do not merge', (WidgetTester tester) async {
297
    final SemanticsTester semantics = SemanticsTester(tester);
298 299

    await tester.pumpWidget(
300
      Directionality(
301
        textDirection: TextDirection.ltr,
302
        child: Semantics(
303
          container: true,
304
          child: Column(
305
            children: <Widget>[
306
              Semantics(
307
                value: 'value one',
308
                child: const SizedBox(
309 310
                  height: 10.0,
                  width: 10.0,
311
                ),
312
              ),
313
              Semantics(
314
                value: 'value two',
315
                child: const SizedBox(
316 317
                  height: 10.0,
                  width: 10.0,
318 319
                ),
              ),
320 321 322 323 324 325
            ],
          ),
        ),
      ),
    );

326
    final TestSemantics expectedSemantics = TestSemantics.root(
327
      children: <TestSemantics>[
328
        TestSemantics.rootChild(
329
          children: <TestSemantics>[
330
            TestSemantics(
331 332 333
              value: 'value one',
              textDirection: TextDirection.ltr,
            ),
334
            TestSemantics(
335 336 337
              value: 'value two',
              textDirection: TextDirection.ltr,
            ),
338
          ],
339
        ),
340 341 342 343
      ],
    );

    expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
344
    semantics.dispose();
345 346 347
  });

  testWidgets('Semantics value and hint can merge', (WidgetTester tester) async {
348
    final SemanticsTester semantics = SemanticsTester(tester);
349 350

    await tester.pumpWidget(
351
      Directionality(
352
        textDirection: TextDirection.ltr,
353
        child: Semantics(
354
          container: true,
355
          child: Column(
356
            children: <Widget>[
357
              Semantics(
358 359
                hint: 'hint',
              ),
360
              Semantics(
361 362 363 364 365 366 367 368
                value: 'value',
              ),
            ],
          ),
        ),
      ),
    );

369
    final TestSemantics expectedSemantics = TestSemantics.root(
370
      children: <TestSemantics>[
371
        TestSemantics.rootChild(
372 373 374
          hint: 'hint',
          value: 'value',
          textDirection: TextDirection.ltr,
375
        ),
376
      ],
377 378 379
    );

    expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
380
    semantics.dispose();
381
  });
382

383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
    testWidgets('Semantics tagForChildren works', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: Semantics(
          container: true,
          tagForChildren: const SemanticsTag('custom tag'),
          child: Column(
            children: <Widget>[
              Semantics(
                container: true,
                child: const Text('child 1'),
              ),
              Semantics(
                container: true,
                child: const Text('child 2'),
              ),
            ],
          ),
        ),
      ),
    );

    final TestSemantics expectedSemantics = TestSemantics.root(
      children: <TestSemantics>[
        TestSemantics.rootChild(
          children: <TestSemantics>[
            TestSemantics(
              label: 'child 1',
              tags: <SemanticsTag>[const SemanticsTag('custom tag')],
              textDirection: TextDirection.ltr,
            ),
            TestSemantics(
              label: 'child 2',
              tags: <SemanticsTag>[const SemanticsTag('custom tag')],
              textDirection: TextDirection.ltr,
            ),
422
          ],
423 424 425 426 427 428 429 430
        ),
      ],
    );

    expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
    semantics.dispose();
  });

431
  testWidgets('Semantics widget supports all actions', (WidgetTester tester) async {
432
    final SemanticsTester semantics = SemanticsTester(tester);
433 434 435 436

    final List<SemanticsAction> performedActions = <SemanticsAction>[];

    await tester.pumpWidget(
437
      Semantics(
438
        container: true,
439
        onDismiss: () => performedActions.add(SemanticsAction.dismiss),
440 441 442 443 444 445 446 447
        onTap: () => performedActions.add(SemanticsAction.tap),
        onLongPress: () => performedActions.add(SemanticsAction.longPress),
        onScrollLeft: () => performedActions.add(SemanticsAction.scrollLeft),
        onScrollRight: () => performedActions.add(SemanticsAction.scrollRight),
        onScrollUp: () => performedActions.add(SemanticsAction.scrollUp),
        onScrollDown: () => performedActions.add(SemanticsAction.scrollDown),
        onIncrease: () => performedActions.add(SemanticsAction.increase),
        onDecrease: () => performedActions.add(SemanticsAction.decrease),
448 449 450
        onCopy: () => performedActions.add(SemanticsAction.copy),
        onCut: () => performedActions.add(SemanticsAction.cut),
        onPaste: () => performedActions.add(SemanticsAction.paste),
451 452
        onMoveCursorForwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorForwardByCharacter),
        onMoveCursorBackwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorBackwardByCharacter),
453
        onSetSelection: (TextSelection _) => performedActions.add(SemanticsAction.setSelection),
454
        onSetText: (String _) => performedActions.add(SemanticsAction.setText),
455 456
        onDidGainAccessibilityFocus: () => performedActions.add(SemanticsAction.didGainAccessibilityFocus),
        onDidLoseAccessibilityFocus: () => performedActions.add(SemanticsAction.didLoseAccessibilityFocus),
457
      ),
458 459 460
    );

    final Set<SemanticsAction> allActions = SemanticsAction.values.values.toSet()
461 462
      ..remove(SemanticsAction.moveCursorForwardByWord)
      ..remove(SemanticsAction.moveCursorBackwardByWord)
463
      ..remove(SemanticsAction.customAction) // customAction is not user-exposed.
464
      ..remove(SemanticsAction.showOnScreen); // showOnScreen is not user-exposed
465

466
    const int expectedId = 1;
467
    final TestSemantics expectedSemantics = TestSemantics.root(
468
      children: <TestSemantics>[
469
        TestSemantics.rootChild(
470 471
          id: expectedId,
          rect: TestSemantics.fullScreen,
472
          actions: allActions.fold<int>(0, (int previous, SemanticsAction action) => previous | action.index),
473 474 475 476 477 478
        ),
      ],
    );
    expect(semantics, hasSemantics(expectedSemantics));

    // Do the actions work?
479
    final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;
480
    int expectedLength = 1;
481
    for (final SemanticsAction action in allActions) {
482 483 484 485 486
      switch (action) {
        case SemanticsAction.moveCursorBackwardByCharacter:
        case SemanticsAction.moveCursorForwardByCharacter:
          semanticsOwner.performAction(expectedId, action, true);
          break;
487
        case SemanticsAction.setSelection:
488
          semanticsOwner.performAction(expectedId, action, <dynamic, dynamic>{
489 490 491 492
            'base': 4,
            'extent': 5,
          });
          break;
493 494 495
        case SemanticsAction.setText:
          semanticsOwner.performAction(expectedId, action, 'text');
          break;
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
        case SemanticsAction.copy:
        case SemanticsAction.customAction:
        case SemanticsAction.cut:
        case SemanticsAction.decrease:
        case SemanticsAction.didGainAccessibilityFocus:
        case SemanticsAction.didLoseAccessibilityFocus:
        case SemanticsAction.dismiss:
        case SemanticsAction.increase:
        case SemanticsAction.longPress:
        case SemanticsAction.moveCursorBackwardByWord:
        case SemanticsAction.moveCursorForwardByWord:
        case SemanticsAction.paste:
        case SemanticsAction.scrollDown:
        case SemanticsAction.scrollLeft:
        case SemanticsAction.scrollRight:
        case SemanticsAction.scrollUp:
        case SemanticsAction.showOnScreen:
        case SemanticsAction.tap:
514
          semanticsOwner.performAction(expectedId, action);
515
          break;
516
      }
517 518 519 520 521 522
      expect(performedActions.length, expectedLength);
      expect(performedActions.last, action);
      expectedLength += 1;
    }

    semantics.dispose();
523
  });
524

525
  testWidgets('Semantics widget supports all flags', (WidgetTester tester) async {
526
    final SemanticsTester semantics = SemanticsTester(tester);
527
    // Note: checked state and toggled state are mutually exclusive.
528
    await tester.pumpWidget(
529
        Semantics(
530
          key: const Key('a'),
531
          container: true,
532
          explicitChildNodes: true,
533 534
          // flags
          enabled: true,
535
          hidden: true,
536 537 538
          checked: true,
          selected: true,
          button: true,
539
          slider: true,
540
          keyboardKey: true,
541
          link: true,
542
          textField: true,
543
          readOnly: true,
544
          focused: true,
545
          focusable: true,
546 547
          inMutuallyExclusiveGroup: true,
          header: true,
548
          obscured: true,
549
          multiline: true,
550 551
          scopesRoute: true,
          namesRoute: true,
552 553
          image: true,
          liveRegion: true,
554
        ),
555
    );
556 557 558
    final List<SemanticsFlag> flags = SemanticsFlag.values.values.toList();
    flags
      ..remove(SemanticsFlag.hasToggledState)
559 560
      ..remove(SemanticsFlag.isToggled)
      ..remove(SemanticsFlag.hasImplicitScrolling);
561

562
    TestSemantics expectedSemantics = TestSemantics.root(
563
      children: <TestSemantics>[
564
        TestSemantics.rootChild(
565
          rect: TestSemantics.fullScreen,
566
          flags: flags,
567 568 569 570 571
        ),
      ],
    );
    expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));

572
    await tester.pumpWidget(Semantics(
573
      key: const Key('b'),
574
      container: true,
575
      scopesRoute: false,
576
    ));
577
    expectedSemantics = TestSemantics.root(
578
      children: <TestSemantics>[
579
        TestSemantics.rootChild(
580 581 582 583 584 585 586
          rect: TestSemantics.fullScreen,
          flags: <SemanticsFlag>[],
        ),
      ],
    );
    expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));

587
    await tester.pumpWidget(
588
      Semantics(
589 590 591 592 593
        key: const Key('c'),
        toggled: true,
      ),
    );

594
    expectedSemantics = TestSemantics.root(
595
      children: <TestSemantics>[
596
        TestSemantics.rootChild(
597 598 599 600 601 602 603 604 605 606
          rect: TestSemantics.fullScreen,
          flags: <SemanticsFlag>[
            SemanticsFlag.hasToggledState,
            SemanticsFlag.isToggled,
          ],
        ),
      ],
    );

    expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
607
    semantics.dispose();
608
  });
609

610
  testWidgets('Actions can be replaced without triggering semantics update', (WidgetTester tester) async {
611
    final SemanticsTester semantics = SemanticsTester(tester);
612
    int semanticsUpdateCount = 0;
613
    final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
614 615
      listener: () {
        semanticsUpdateCount += 1;
616
      },
617 618 619 620 621
    );

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

    await tester.pumpWidget(
622
      Semantics(
623 624 625 626 627
        container: true,
        onTap: () => performedActions.add('first'),
      ),
    );

628
    const int expectedId = 1;
629
    final TestSemantics expectedSemantics = TestSemantics.root(
630
      children: <TestSemantics>[
631
        TestSemantics.rootChild(
632 633 634 635 636 637 638
          id: expectedId,
          rect: TestSemantics.fullScreen,
          actions: SemanticsAction.tap.index,
        ),
      ],
    );

639
    final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;
640 641 642 643 644 645 646 647 648 649 650

    expect(semantics, hasSemantics(expectedSemantics));
    semanticsOwner.performAction(expectedId, SemanticsAction.tap);
    expect(semanticsUpdateCount, 1);
    expect(performedActions, <String>['first']);

    semanticsUpdateCount = 0;
    performedActions.clear();

    // Updating existing handler should not trigger semantics update
    await tester.pumpWidget(
651
      Semantics(
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
        container: true,
        onTap: () => performedActions.add('second'),
      ),
    );

    expect(semantics, hasSemantics(expectedSemantics));
    semanticsOwner.performAction(expectedId, SemanticsAction.tap);
    expect(semanticsUpdateCount, 0);
    expect(performedActions, <String>['second']);

    semanticsUpdateCount = 0;
    performedActions.clear();

    // Adding a handler works
    await tester.pumpWidget(
667
      Semantics(
668 669 670 671 672 673
        container: true,
        onTap: () => performedActions.add('second'),
        onLongPress: () => performedActions.add('longPress'),
      ),
    );

674
    final TestSemantics expectedSemanticsWithLongPress = TestSemantics.root(
675
      children: <TestSemantics>[
676
        TestSemantics.rootChild(
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
          id: expectedId,
          rect: TestSemantics.fullScreen,
          actions: SemanticsAction.tap.index | SemanticsAction.longPress.index,
        ),
      ],
    );

    expect(semantics, hasSemantics(expectedSemanticsWithLongPress));
    semanticsOwner.performAction(expectedId, SemanticsAction.longPress);
    expect(semanticsUpdateCount, 1);
    expect(performedActions, <String>['longPress']);

    semanticsUpdateCount = 0;
    performedActions.clear();

    // Removing a handler works
    await tester.pumpWidget(
694
      Semantics(
695 696 697 698 699 700 701 702
        container: true,
        onTap: () => performedActions.add('second'),
      ),
    );

    expect(semantics, hasSemantics(expectedSemantics));
    expect(semanticsUpdateCount, 1);

703
    handle.dispose();
704 705
    semantics.dispose();
  });
706

707 708
  testWidgets('onTapHint and onLongPressHint create custom actions', (WidgetTester tester) async {
    final SemanticsHandle semantics = tester.ensureSemantics();
709
    await tester.pumpWidget(Semantics(
710
      container: true,
711
      onTap: () { },
712 713 714
      onTapHint: 'test',
    ));

715
    expect(tester.getSemantics(find.byType(Semantics)), matchesSemantics(
716
      hasTapAction: true,
717
      onTapHint: 'test',
718 719
    ));

720
    await tester.pumpWidget(Semantics(
721
      container: true,
722
      onLongPress: () { },
723 724 725
      onLongPressHint: 'foo',
    ));

726
    expect(tester.getSemantics(find.byType(Semantics)), matchesSemantics(
727
      hasLongPressAction: true,
728
      onLongPressHint: 'foo',
729 730 731 732 733 734
    ));
    semantics.dispose();
  });

  testWidgets('CustomSemanticsActions can be added to a Semantics widget', (WidgetTester tester) async {
    final SemanticsHandle semantics = tester.ensureSemantics();
735
    await tester.pumpWidget(Semantics(
736 737
      container: true,
      customSemanticsActions: <CustomSemanticsAction, VoidCallback>{
738 739
        const CustomSemanticsAction(label: 'foo'): () { },
        const CustomSemanticsAction(label: 'bar'): () { },
740 741 742
      },
    ));

743
    expect(tester.getSemantics(find.byType(Semantics)), matchesSemantics(
744 745 746 747 748 749 750 751
      customActions: <CustomSemanticsAction>[
        const CustomSemanticsAction(label: 'bar'),
        const CustomSemanticsAction(label: 'foo'),
      ],
    ));
    semantics.dispose();
  });

752
  testWidgets('Increased/decreased values are annotated', (WidgetTester tester) async {
753
    final SemanticsTester semantics = SemanticsTester(tester);
754 755

    await tester.pumpWidget(
756
      Directionality(
757
        textDirection: TextDirection.ltr,
758
        child: Semantics(
759 760 761 762
          container: true,
          value: '10s',
          increasedValue: '11s',
          decreasedValue: '9s',
763 764
          onIncrease: () => () { },
          onDecrease: () => () { },
765 766 767 768
        ),
      ),
    );

769
    expect(semantics, hasSemantics(TestSemantics.root(
770
      children: <TestSemantics>[
771
        TestSemantics.rootChild(
772 773 774 775 776 777 778 779 780 781 782
          actions: SemanticsAction.increase.index | SemanticsAction.decrease.index,
          textDirection: TextDirection.ltr,
          value: '10s',
          increasedValue: '11s',
          decreasedValue: '9s',
        ),
      ],
    ), ignoreTransform: true, ignoreRect: true, ignoreId: true));

    semantics.dispose();
  });
783 784

  testWidgets('Semantics widgets built in a widget tree are sorted properly', (WidgetTester tester) async {
785
    final SemanticsTester semantics = SemanticsTester(tester);
786
    int semanticsUpdateCount = 0;
787
    final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
788 789
      listener: () {
        semanticsUpdateCount += 1;
790
      },
791 792
    );
    await tester.pumpWidget(
793
      Directionality(
794
        textDirection: TextDirection.ltr,
795
        child: Semantics(
796 797
          sortKey: const CustomSortKey(0.0),
          explicitChildNodes: true,
798
          child: Column(
799
            children: <Widget>[
800 801 802
              Semantics(sortKey: const CustomSortKey(3.0), child: const Text('Label 1')),
              Semantics(sortKey: const CustomSortKey(2.0), child: const Text('Label 2')),
              Semantics(
803 804
                sortKey: const CustomSortKey(1.0),
                explicitChildNodes: true,
805
                child: Row(
806
                  children: <Widget>[
807 808 809
                    Semantics(sortKey: const OrdinalSortKey(3.0), child: const Text('Label 3')),
                    Semantics(sortKey: const OrdinalSortKey(2.0), child: const Text('Label 4')),
                    Semantics(sortKey: const OrdinalSortKey(1.0), child: const Text('Label 5')),
810 811 812 813 814 815 816 817 818 819
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
    expect(semanticsUpdateCount, 1);
    expect(semantics, hasSemantics(
820
      TestSemantics.root(
821
        children: <TestSemantics>[
822
          TestSemantics(
823
            id: 1,
824
            children: <TestSemantics>[
825
              TestSemantics(
826
                id: 4,
827
                children: <TestSemantics>[
828
                  TestSemantics(
829 830
                    id: 7,
                    label: r'Label 5',
831 832
                    textDirection: TextDirection.ltr,
                  ),
833
                  TestSemantics(
834
                    id: 6,
835 836 837
                    label: r'Label 4',
                    textDirection: TextDirection.ltr,
                  ),
838
                  TestSemantics(
839 840
                    id: 5,
                    label: r'Label 3',
841 842 843 844
                    textDirection: TextDirection.ltr,
                  ),
                ],
              ),
845
              TestSemantics(
846 847 848 849
                id: 3,
                label: r'Label 2',
                textDirection: TextDirection.ltr,
              ),
850
              TestSemantics(
851 852 853 854
                id: 2,
                label: r'Label 1',
                textDirection: TextDirection.ltr,
              ),
855 856 857
            ],
          ),
        ],
858 859 860 861
      ),
      ignoreTransform: true,
      ignoreRect: true,
    ));
862 863

    handle.dispose();
864 865 866 867
    semantics.dispose();
  });

  testWidgets('Semantics widgets built with explicit sort orders are sorted properly', (WidgetTester tester) async {
868
    final SemanticsTester semantics = SemanticsTester(tester);
869
    int semanticsUpdateCount = 0;
870
    final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
871 872
      listener: () {
        semanticsUpdateCount += 1;
873
      },
874 875
    );
    await tester.pumpWidget(
876
      Directionality(
877
        textDirection: TextDirection.ltr,
878
        child: Row(
879
          children: <Widget>[
880
            Semantics(
881
              sortKey: const CustomSortKey(3.0),
882 883
              child: const Text('Label 1'),
            ),
884
            Semantics(
885
              sortKey: const CustomSortKey(1.0),
886 887
              child: const Text('Label 2'),
            ),
888
            Semantics(
889 890
              sortKey: const CustomSortKey(2.0),
              child: const Text('Label 3'),
891 892 893 894 895 896 897
            ),
          ],
        ),
      ),
    );
    expect(semanticsUpdateCount, 1);
    expect(semantics, hasSemantics(
898
      TestSemantics.root(
899
        children: <TestSemantics>[
900
          TestSemantics(
901
            id: 2,
902 903 904
            label: r'Label 2',
            textDirection: TextDirection.ltr,
          ),
905
          TestSemantics(
906 907
            id: 3,
            label: r'Label 3',
908
            textDirection: TextDirection.ltr,
909
          ),
910
          TestSemantics(
911 912 913
            id: 1,
            label: r'Label 1',
            textDirection: TextDirection.ltr,
914 915
          ),
        ],
916 917 918 919
      ),
      ignoreTransform: true,
      ignoreRect: true,
    ));
920

921
    handle.dispose();
922 923 924 925
    semantics.dispose();
  });

  testWidgets('Semantics widgets without sort orders are sorted properly', (WidgetTester tester) async {
926
    final SemanticsTester semantics = SemanticsTester(tester);
927
    int semanticsUpdateCount = 0;
928
    final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
929 930
      listener: () {
        semanticsUpdateCount += 1;
931
      },
932 933
    );
    await tester.pumpWidget(
934
      Directionality(
935
        textDirection: TextDirection.ltr,
936
        child: Column(
937 938 939
          children: <Widget>[
            const Text('Label 1'),
            const Text('Label 2'),
940
            Row(
941
              children: const <Widget>[
942 943 944
                Text('Label 3'),
                Text('Label 4'),
                Text('Label 5'),
945 946 947 948 949 950 951 952
              ],
            ),
          ],
        ),
      ),
    );
    expect(semanticsUpdateCount, 1);
    expect(semantics, hasSemantics(
953
      TestSemantics(
954
        children: <TestSemantics>[
955
          TestSemantics(
956 957 958
            label: r'Label 1',
            textDirection: TextDirection.ltr,
          ),
959
          TestSemantics(
960 961 962
            label: r'Label 2',
            textDirection: TextDirection.ltr,
          ),
963
          TestSemantics(
964 965 966
            label: r'Label 3',
            textDirection: TextDirection.ltr,
          ),
967
          TestSemantics(
968 969 970
            label: r'Label 4',
            textDirection: TextDirection.ltr,
          ),
971
          TestSemantics(
972 973 974 975
            label: r'Label 5',
            textDirection: TextDirection.ltr,
          ),
        ],
976 977 978 979 980
      ),
      ignoreTransform: true,
      ignoreRect: true,
      ignoreId: true,
    ));
981 982

    handle.dispose();
983 984 985 986
    semantics.dispose();
  });

  testWidgets('Semantics widgets that are transformed are sorted properly', (WidgetTester tester) async {
987
    final SemanticsTester semantics = SemanticsTester(tester);
988
    int semanticsUpdateCount = 0;
989
    final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
990 991
      listener: () {
        semanticsUpdateCount += 1;
992
      },
993 994
    );
    await tester.pumpWidget(
995
      Directionality(
996
        textDirection: TextDirection.ltr,
997
        child: Column(
998 999 1000
          children: <Widget>[
            const Text('Label 1'),
            const Text('Label 2'),
1001
            Transform.rotate(
1002
              angle: pi / 2.0,
1003
              child: Row(
1004
                children: const <Widget>[
1005 1006 1007
                  Text('Label 3'),
                  Text('Label 4'),
                  Text('Label 5'),
1008 1009 1010 1011 1012 1013 1014 1015 1016
                ],
              ),
            ),
          ],
        ),
      ),
    );
    expect(semanticsUpdateCount, 1);
    expect(semantics, hasSemantics(
1017
      TestSemantics(
1018
        children: <TestSemantics>[
1019
          TestSemantics(
1020 1021 1022
            label: r'Label 1',
            textDirection: TextDirection.ltr,
          ),
1023
          TestSemantics(
1024 1025 1026
            label: r'Label 2',
            textDirection: TextDirection.ltr,
          ),
1027
          TestSemantics(
1028 1029 1030
            label: r'Label 3',
            textDirection: TextDirection.ltr,
          ),
1031
          TestSemantics(
1032 1033 1034
            label: r'Label 4',
            textDirection: TextDirection.ltr,
          ),
1035
          TestSemantics(
1036 1037 1038 1039
            label: r'Label 5',
            textDirection: TextDirection.ltr,
          ),
        ],
1040 1041 1042 1043 1044
      ),
      ignoreTransform: true,
      ignoreRect: true,
      ignoreId: true,
    ));
1045 1046

    handle.dispose();
1047 1048 1049
    semantics.dispose();
  });

1050
  testWidgets('Semantics widgets without sort orders are sorted properly when no Directionality is present', (WidgetTester tester) async {
1051
    final SemanticsTester semantics = SemanticsTester(tester);
1052
    int semanticsUpdateCount = 0;
1053
    final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(listener: () {
1054 1055 1056
      semanticsUpdateCount += 1;
    });
    await tester.pumpWidget(
1057
      Stack(
1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
        alignment: Alignment.center,
        children: <Widget>[
          // Set this up so that the placeholder takes up the whole screen,
          // and place the positioned boxes so that if we traverse in the
          // geometric order, we would go from box [4, 3, 2, 1, 0], but if we
          // go in child order, then we go from box [4, 1, 2, 3, 0]. We're verifying
          // that we go in child order here, not geometric order, since there
          // is no directionality, so we don't have a geometric opinion about
          // horizontal order. We do still want to sort vertically, however,
          // which is why the order isn't [0, 1, 2, 3, 4].
1068
          Semantics(
1069 1070 1071
            button: true,
            child: const Placeholder(),
          ),
1072
          Positioned(
1073 1074
            top: 200.0,
            left: 100.0,
1075
            child: Semantics( // Box 0
1076 1077 1078 1079
              button: true,
              child: const SizedBox(width: 30.0, height: 30.0),
            ),
          ),
1080
          Positioned(
1081 1082
            top: 100.0,
            left: 200.0,
1083
            child: Semantics( // Box 1
1084 1085 1086 1087
              button: true,
              child: const SizedBox(width: 30.0, height: 30.0),
            ),
          ),
1088
          Positioned(
1089 1090
            top: 100.0,
            left: 100.0,
1091
            child: Semantics( // Box 2
1092 1093 1094 1095
              button: true,
              child: const SizedBox(width: 30.0, height: 30.0),
            ),
          ),
1096
          Positioned(
1097 1098
            top: 100.0,
            left: 0.0,
1099
            child: Semantics( // Box 3
1100 1101 1102 1103
              button: true,
              child: const SizedBox(width: 30.0, height: 30.0),
            ),
          ),
1104
          Positioned(
1105 1106
            top: 10.0,
            left: 100.0,
1107
            child: Semantics( // Box 4
1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
              button: true,
              child: const SizedBox(width: 30.0, height: 30.0),
            ),
          ),
        ],
      ),
    );
    expect(semanticsUpdateCount, 1);
    expect(
      semantics,
      hasSemantics(
1119
        TestSemantics(
1120
          children: <TestSemantics>[
1121
            TestSemantics(
1122 1123
              flags: <SemanticsFlag>[SemanticsFlag.isButton],
            ),
1124
            TestSemantics(
1125 1126
              flags: <SemanticsFlag>[SemanticsFlag.isButton],
            ),
1127
            TestSemantics(
1128 1129
              flags: <SemanticsFlag>[SemanticsFlag.isButton],
            ),
1130
            TestSemantics(
1131 1132
              flags: <SemanticsFlag>[SemanticsFlag.isButton],
            ),
1133
            TestSemantics(
1134 1135
              flags: <SemanticsFlag>[SemanticsFlag.isButton],
            ),
1136
            TestSemantics(
1137 1138 1139 1140 1141 1142
              flags: <SemanticsFlag>[SemanticsFlag.isButton],
            ),
          ],
        ),
        ignoreTransform: true,
        ignoreRect: true,
1143 1144
        ignoreId: true,
      ),
1145
    );
1146 1147

    handle.dispose();
1148 1149
    semantics.dispose();
  });
1150 1151

  testWidgets('Semantics excludeSemantics ignores children', (WidgetTester tester) async {
1152 1153
    final SemanticsTester semantics = SemanticsTester(tester);
    await tester.pumpWidget(Semantics(
1154 1155 1156
      label: 'label',
      excludeSemantics: true,
      textDirection: TextDirection.ltr,
1157
      child: Semantics(
1158 1159 1160 1161 1162 1163
        label: 'other label',
        textDirection: TextDirection.ltr,
      ),
    ));

    expect(semantics, hasSemantics(
1164
      TestSemantics(
1165
        children: <TestSemantics>[
1166
          TestSemantics(
1167 1168 1169 1170
            label: 'label',
            textDirection: TextDirection.ltr,
          ),
        ],
1171 1172 1173 1174 1175
      ),
      ignoreId: true,
      ignoreRect: true,
      ignoreTransform: true,
    ));
1176 1177
    semantics.dispose();
  });
1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564

  testWidgets('Can change handlers', (WidgetTester tester) async {
    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onTap: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasTapAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onDismiss: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasDismissAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onLongPress: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasLongPressAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onScrollLeft: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasScrollLeftAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onScrollRight: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasScrollRightAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onScrollUp: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasScrollUpAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onScrollDown: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasScrollDownAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onIncrease: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasIncreaseAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onDecrease: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasDecreaseAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onCopy: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasCopyAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onCut: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasCutAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onPaste: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasPasteAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onSetSelection: (TextSelection _) {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasSetSelectionAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onDidGainAccessibilityFocus: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasDidGainAccessibilityFocusAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));


    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
      onDidLoseAccessibilityFocus: () {},
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      hasDidLoseAccessibilityFocusAction: true,
      textDirection: TextDirection.ltr,
    ));

    await tester.pumpWidget(Semantics(
      container: true,
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));

    expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
      label: 'foo',
      textDirection: TextDirection.ltr,
    ));
  });
1565 1566 1567
}

class CustomSortKey extends OrdinalSortKey {
1568
  const CustomSortKey(double order, {String? name}) : super(order, name: name);
1569
}