semantics_test.dart 19.7 KB
Newer Older
1 2 3 4 5
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/rendering.dart';
6
import 'package:flutter/semantics.dart';
7
import 'package:vector_math/vector_math_64.dart';
8
import 'package:flutter_test/flutter_test.dart';
9

10
import '../rendering/rendering_tester.dart';
11

12 13

void main() {
14 15 16 17
  setUp(() {
    debugResetSemanticsIdCounter();
  });

18
  group('SemanticsNode', () {
19 20 21
    const SemanticsTag tag1 = SemanticsTag('Tag One');
    const SemanticsTag tag2 = SemanticsTag('Tag Two');
    const SemanticsTag tag3 = SemanticsTag('Tag Three');
22 23

    test('tagging', () {
24
      final SemanticsNode node = SemanticsNode();
25

26 27
      expect(node.isTagged(tag1), isFalse);
      expect(node.isTagged(tag2), isFalse);
28

29
      node.tags = Set<SemanticsTag>()..add(tag1);
30 31
      expect(node.isTagged(tag1), isTrue);
      expect(node.isTagged(tag2), isFalse);
32

33 34 35
      node.tags.add(tag2);
      expect(node.isTagged(tag1), isTrue);
      expect(node.isTagged(tag2), isTrue);
36 37 38
    });

    test('getSemanticsData includes tags', () {
39
      final Set<SemanticsTag> tags = Set<SemanticsTag>()
40 41 42
        ..add(tag1)
        ..add(tag2);

43 44
      final SemanticsNode node = SemanticsNode()
        ..rect = Rect.fromLTRB(0.0, 0.0, 10.0, 10.0)
45
        ..tags = tags;
46

47
      expect(node.getSemanticsData().tags, tags);
48

49
      tags.add(tag3);
50

51
      final SemanticsConfiguration config = SemanticsConfiguration()
52
        ..isSemanticBoundary = true
53
        ..isMergingSemanticsOfDescendants = true;
54

55 56 57
      node.updateWith(
        config: config,
        childrenInInversePaintOrder: <SemanticsNode>[
58
          SemanticsNode()
59
            ..isMergedIntoParent = true
60
            ..rect = Rect.fromLTRB(5.0, 5.0, 10.0, 10.0)
61 62 63
            ..tags = tags,
        ],
      );
64

65
      expect(node.getSemanticsData().tags, tags);
66
    });
67

68
    test('after markNeedsSemanticsUpdate() all render objects between two semantic boundaries are asked for annotations', () {
69 70 71
      renderer.pipelineOwner.ensureSemantics();

      TestRender middle;
72
      final TestRender root = TestRender(
73
        hasTapAction: true,
74
        isSemanticBoundary: true,
75
        child: TestRender(
76
          hasLongPressAction: true,
77
          isSemanticBoundary: false,
78
          child: middle = TestRender(
79
            hasScrollLeftAction: true,
80
            isSemanticBoundary: false,
81
            child: TestRender(
82
              hasScrollRightAction: true,
83
              isSemanticBoundary: false,
84
              child: TestRender(
85
                hasScrollUpAction: true,
86 87 88 89 90 91 92 93 94 95 96 97 98
                isSemanticBoundary: true,
              )
            )
          )
        )
      );

      layout(root);
      pumpFrame(phase: EnginePhase.flushSemantics);

      int expectedActions = SemanticsAction.tap.index | SemanticsAction.longPress.index | SemanticsAction.scrollLeft.index | SemanticsAction.scrollRight.index;
      expect(root.debugSemantics.getSemanticsData().actions, expectedActions);

99 100 101
      middle
        ..hasScrollLeftAction = false
        ..hasScrollDownAction = true;
102
      middle.markNeedsSemanticsUpdate();
103 104 105 106 107 108

      pumpFrame(phase: EnginePhase.flushSemantics);

      expectedActions = SemanticsAction.tap.index | SemanticsAction.longPress.index | SemanticsAction.scrollDown.index | SemanticsAction.scrollRight.index;
      expect(root.debugSemantics.getSemanticsData().actions, expectedActions);
    });
109
  });
110 111

  test('toStringDeep() does not throw with transform == null', () {
112 113 114 115 116 117
    final SemanticsNode child1 = SemanticsNode()
      ..rect = Rect.fromLTRB(0.0, 0.0, 5.0, 5.0);
    final SemanticsNode child2 = SemanticsNode()
      ..rect = Rect.fromLTRB(5.0, 0.0, 10.0, 5.0);
    final SemanticsNode root = SemanticsNode()
      ..rect = Rect.fromLTRB(0.0, 0.0, 10.0, 5.0);
118 119 120 121
    root.updateWith(
      config: null,
      childrenInInversePaintOrder: <SemanticsNode>[child1, child2],
    );
122 123 124 125 126

    expect(root.transform, isNull);
    expect(child1.transform, isNull);
    expect(child2.transform, isNull);

127
    expect(
128
      root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversalOrder),
129 130 131 132 133 134 135 136 137 138 139 140 141 142
      'SemanticsNode#3\n'
      ' │ STALE\n'
      ' │ owner: null\n'
      ' │ Rect.fromLTRB(0.0, 0.0, 10.0, 5.0)\n'
      ' │\n'
      ' ├─SemanticsNode#1\n'
      ' │   STALE\n'
      ' │   owner: null\n'
      ' │   Rect.fromLTRB(0.0, 0.0, 5.0, 5.0)\n'
      ' │\n'
      ' └─SemanticsNode#2\n'
      '     STALE\n'
      '     owner: null\n'
      '     Rect.fromLTRB(5.0, 0.0, 10.0, 5.0)\n'
143 144 145
    );
  });

146 147 148 149 150 151 152 153 154 155 156 157
  test('Incompatible OrdinalSortKey throw AssertionError when compared', () {
    // Different types.
    expect(() {
      const OrdinalSortKey(0.0).compareTo(const CustomSortKey(0.0));
    }, throwsAssertionError);

    // Different names.
    expect(() {
      const OrdinalSortKey(0.0, name: 'a').compareTo(const OrdinalSortKey(0.0, name: 'b'));
    }, throwsAssertionError);
  });

158
  test('OrdinalSortKey compares correctly', () {
159 160 161 162 163
    const List<List<SemanticsSortKey>> tests = <List<SemanticsSortKey>>[
      <SemanticsSortKey>[OrdinalSortKey(0.0), OrdinalSortKey(0.0)],
      <SemanticsSortKey>[OrdinalSortKey(0.0), OrdinalSortKey(1.0)],
      <SemanticsSortKey>[OrdinalSortKey(1.0), OrdinalSortKey(0.0)],
      <SemanticsSortKey>[OrdinalSortKey(1.0), OrdinalSortKey(1.0)],
164
    ];
165
    final List<int> expectedResults = <int>[0, -1, 1, 0];
166 167 168 169 170 171 172 173 174
    assert(tests.length == expectedResults.length);
    final List<int> results = <int>[];
    for (List<SemanticsSortKey> tuple in tests) {
      results.add(tuple[0].compareTo(tuple[1]));
    }
    expect(results, orderedEquals(expectedResults));
  });

  test('OrdinalSortKey compares correctly', () {
175 176 177 178 179
    const List<List<SemanticsSortKey>> tests = <List<SemanticsSortKey>>[
      <SemanticsSortKey>[OrdinalSortKey(0.0), OrdinalSortKey(0.0)],
      <SemanticsSortKey>[OrdinalSortKey(0.0), OrdinalSortKey(1.0)],
      <SemanticsSortKey>[OrdinalSortKey(1.0), OrdinalSortKey(0.0)],
      <SemanticsSortKey>[OrdinalSortKey(1.0), OrdinalSortKey(1.0)],
180
    ];
181
    final List<int> expectedResults = <int>[0, -1, 1, 0];
182 183 184 185 186 187 188 189
    assert(tests.length == expectedResults.length);
    final List<int> results = <int>[];
    for (List<SemanticsSortKey> tuple in tests) {
      results.add(tuple[0].compareTo(tuple[1]));
    }
    expect(results, orderedEquals(expectedResults));
  });

190
  test('toStringDeep respects childOrder parameter', () {
191 192 193 194 195 196
    final SemanticsNode child1 = SemanticsNode()
      ..rect = Rect.fromLTRB(15.0, 0.0, 20.0, 5.0);
    final SemanticsNode child2 = SemanticsNode()
      ..rect = Rect.fromLTRB(10.0, 0.0, 15.0, 5.0);
    final SemanticsNode root = SemanticsNode()
      ..rect = Rect.fromLTRB(0.0, 0.0, 20.0, 5.0);
197 198 199 200
    root.updateWith(
      config: null,
      childrenInInversePaintOrder: <SemanticsNode>[child1, child2],
    );
201
    expect(
202
      root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversalOrder),
203 204 205 206 207
      'SemanticsNode#3\n'
      ' │ STALE\n'
      ' │ owner: null\n'
      ' │ Rect.fromLTRB(0.0, 0.0, 20.0, 5.0)\n'
      ' │\n'
208
      ' ├─SemanticsNode#1\n'
209 210
      ' │   STALE\n'
      ' │   owner: null\n'
211
      ' │   Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n'
212
      ' │\n'
213
      ' └─SemanticsNode#2\n'
214 215
      '     STALE\n'
      '     owner: null\n'
216
      '     Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n'
217 218 219 220
    );

    expect(
      root.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest),
221 222 223 224 225 226 227 228 229 230 231 232 233 234
      'SemanticsNode#3\n'
      ' │ STALE\n'
      ' │ owner: null\n'
      ' │ Rect.fromLTRB(0.0, 0.0, 20.0, 5.0)\n'
      ' │\n'
      ' ├─SemanticsNode#1\n'
      ' │   STALE\n'
      ' │   owner: null\n'
      ' │   Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n'
      ' │\n'
      ' └─SemanticsNode#2\n'
      '     STALE\n'
      '     owner: null\n'
      '     Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n'
235 236
    );

237 238
    final SemanticsNode child3 = SemanticsNode()
      ..rect = Rect.fromLTRB(0.0, 0.0, 10.0, 5.0);
239 240 241
    child3.updateWith(
      config: null,
      childrenInInversePaintOrder: <SemanticsNode>[
242 243 244 245
        SemanticsNode()
          ..rect = Rect.fromLTRB(5.0, 0.0, 10.0, 5.0),
        SemanticsNode()
          ..rect = Rect.fromLTRB(0.0, 0.0, 5.0, 5.0),
246 247
      ],
    );
248

249 250
    final SemanticsNode rootComplex = SemanticsNode()
      ..rect = Rect.fromLTRB(0.0, 0.0, 25.0, 5.0);
251 252 253 254
    rootComplex.updateWith(
        config: null,
        childrenInInversePaintOrder: <SemanticsNode>[child1, child2, child3]
    );
255 256

    expect(
257
      rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversalOrder),
258 259 260 261 262
      'SemanticsNode#7\n'
      ' │ STALE\n'
      ' │ owner: null\n'
      ' │ Rect.fromLTRB(0.0, 0.0, 25.0, 5.0)\n'
      ' │\n'
263 264 265 266
      ' ├─SemanticsNode#1\n'
      ' │   STALE\n'
      ' │   owner: null\n'
      ' │   Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n'
267 268 269 270 271 272
      ' │\n'
      ' ├─SemanticsNode#2\n'
      ' │   STALE\n'
      ' │   owner: null\n'
      ' │   Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n'
      ' │\n'
273 274 275 276 277 278 279 280 281 282 283 284 285 286
      ' └─SemanticsNode#4\n'
      '   │ STALE\n'
      '   │ owner: null\n'
      '   │ Rect.fromLTRB(0.0, 0.0, 10.0, 5.0)\n'
      '   │\n'
      '   ├─SemanticsNode#5\n'
      '   │   STALE\n'
      '   │   owner: null\n'
      '   │   Rect.fromLTRB(5.0, 0.0, 10.0, 5.0)\n'
      '   │\n'
      '   └─SemanticsNode#6\n'
      '       STALE\n'
      '       owner: null\n'
      '       Rect.fromLTRB(0.0, 0.0, 5.0, 5.0)\n'
287 288 289 290
    );

    expect(
      rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest),
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
      'SemanticsNode#7\n'
      ' │ STALE\n'
      ' │ owner: null\n'
      ' │ Rect.fromLTRB(0.0, 0.0, 25.0, 5.0)\n'
      ' │\n'
      ' ├─SemanticsNode#1\n'
      ' │   STALE\n'
      ' │   owner: null\n'
      ' │   Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n'
      ' │\n'
      ' ├─SemanticsNode#2\n'
      ' │   STALE\n'
      ' │   owner: null\n'
      ' │   Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n'
      ' │\n'
      ' └─SemanticsNode#4\n'
      '   │ STALE\n'
      '   │ owner: null\n'
      '   │ Rect.fromLTRB(0.0, 0.0, 10.0, 5.0)\n'
      '   │\n'
      '   ├─SemanticsNode#5\n'
      '   │   STALE\n'
      '   │   owner: null\n'
      '   │   Rect.fromLTRB(5.0, 0.0, 10.0, 5.0)\n'
      '   │\n'
      '   └─SemanticsNode#6\n'
      '       STALE\n'
      '       owner: null\n'
      '       Rect.fromLTRB(0.0, 0.0, 5.0, 5.0)\n'
320 321 322 323
    );
  });

  test('debug properties', () {
324
    final SemanticsNode minimalProperties = SemanticsNode();
325
    expect(
326
      minimalProperties.toStringDeep(),
327 328 329
      'SemanticsNode#1\n'
      '   Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)\n'
      '   invisible\n'
330 331
    );

332 333
    expect(
      minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
334 335 336 337 338 339
      'SemanticsNode#1\n'
      '   owner: null\n'
      '   isMergedIntoParent: false\n'
      '   mergeAllDescendantsIntoThisNode: false\n'
      '   Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)\n'
      '   actions: []\n'
340
      '   customActions: []\n'
341 342
      '   flags: []\n'
      '   invisible\n'
343
      '   isHidden: false\n'
344 345 346 347 348 349
      '   label: ""\n'
      '   value: ""\n'
      '   increasedValue: ""\n'
      '   decreasedValue: ""\n'
      '   hint: ""\n'
      '   textDirection: null\n'
350
      '   sortKey: null\n'
351 352
      '   scrollChildren: null\n'
      '   scrollIndex: null\n'
353 354 355
      '   scrollExtentMin: null\n'
      '   scrollPosition: null\n'
      '   scrollExtentMax: null\n'
356 357
    );

358
    final SemanticsConfiguration config = SemanticsConfiguration()
359
      ..isSemanticBoundary = true
360
      ..isMergingSemanticsOfDescendants = true
361 362 363
      ..onScrollUp = () { }
      ..onLongPress = () { }
      ..onShowOnScreen = () { }
364 365
      ..isChecked = false
      ..isSelected = true
366
      ..isButton = true
367
      ..label = 'Use all the properties'
368
      ..textDirection = TextDirection.rtl
369
      ..sortKey = const OrdinalSortKey(1.0);
370 371 372
    final SemanticsNode allProperties = SemanticsNode()
      ..rect = Rect.fromLTWH(50.0, 10.0, 20.0, 30.0)
      ..transform = Matrix4.translation(Vector3(10.0, 10.0, 0.0))
373
      ..updateWith(config: config, childrenInInversePaintOrder: null);
374 375
    expect(
      allProperties.toStringDeep(),
376 377 378 379 380 381 382 383 384 385
      equalsIgnoringHashCodes(
          'SemanticsNode#2\n'
          '   STALE\n'
          '   owner: null\n'
          '   merge boundary ⛔️\n'
          '   Rect.fromLTRB(60.0, 20.0, 80.0, 50.0)\n'
          '   actions: longPress, scrollUp, showOnScreen\n'
          '   flags: hasCheckedState, isSelected, isButton\n'
          '   label: "Use all the properties"\n'
          '   textDirection: rtl\n'
386
          '   sortKey: OrdinalSortKey#19df5(order: 1.0)\n'
387
      ),
388 389 390
    );
    expect(
      allProperties.getSemanticsData().toString(),
391
      'SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), [1.0,0.0,0.0,10.0; 0.0,1.0,0.0,10.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0], actions: [longPress, scrollUp, showOnScreen], flags: [hasCheckedState, isSelected, isButton], label: "Use all the properties", textDirection: rtl)',
392 393
    );

394 395 396
    final SemanticsNode scaled = SemanticsNode()
      ..rect = Rect.fromLTWH(50.0, 10.0, 20.0, 30.0)
      ..transform = Matrix4.diagonal3(Vector3(10.0, 10.0, 1.0));
397 398
    expect(
      scaled.toStringDeep(),
399 400 401 402
      'SemanticsNode#3\n'
      '   STALE\n'
      '   owner: null\n'
      '   Rect.fromLTRB(50.0, 10.0, 70.0, 40.0) scaled by 10.0x\n',
403 404 405 406
    );
    expect(
      scaled.getSemanticsData().toString(),
      'SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), [10.0,0.0,0.0,0.0; 0.0,10.0,0.0,0.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0])',
407 408
    );
  });
409

410
  test('Custom actions debug properties', () {
411
    final SemanticsConfiguration configuration = SemanticsConfiguration();
412 413 414
    const CustomSemanticsAction action1 = CustomSemanticsAction(label: 'action1');
    const CustomSemanticsAction action2 = CustomSemanticsAction(label: 'action2');
    const CustomSemanticsAction action3 = CustomSemanticsAction(label: 'action3');
415 416 417 418 419
    configuration.customSemanticsActions = <CustomSemanticsAction, VoidCallback>{
      action1: () {},
      action2: () {},
      action3: () {},
    };
420
    final SemanticsNode actionNode = SemanticsNode();
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
    actionNode.updateWith(config: configuration);

    expect(
      actionNode.toStringDeep(minLevel: DiagnosticLevel.hidden),
      'SemanticsNode#1\n'
      '   STALE\n'
      '   owner: null\n'
      '   isMergedIntoParent: false\n'
      '   mergeAllDescendantsIntoThisNode: false\n'
      '   Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)\n'
      '   actions: customAction\n'
      '   customActions: action1, action2, action3\n'
      '   flags: []\n'
      '   invisible\n'
      '   isHidden: false\n'
      '   label: ""\n'
      '   value: ""\n'
      '   increasedValue: ""\n'
      '   decreasedValue: ""\n'
      '   hint: ""\n'
      '   textDirection: null\n'
      '   sortKey: null\n'
443 444
      '   scrollChildren: null\n'
      '   scrollIndex: null\n'
445 446 447 448 449 450 451
      '   scrollExtentMin: null\n'
      '   scrollPosition: null\n'
      '   scrollExtentMax: null\n'
    );

  });

452
  test('SemanticsConfiguration getter/setter', () {
453
    final SemanticsConfiguration config = SemanticsConfiguration();
454
    const CustomSemanticsAction customAction = CustomSemanticsAction(label: 'test');
455 456 457 458

    expect(config.isSemanticBoundary, isFalse);
    expect(config.isButton, isFalse);
    expect(config.isMergingSemanticsOfDescendants, isFalse);
459 460
    expect(config.isEnabled, null);
    expect(config.isChecked, null);
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    expect(config.isSelected, isFalse);
    expect(config.isBlockingSemanticsOfPreviouslyPaintedNodes, isFalse);
    expect(config.isFocused, isFalse);
    expect(config.isTextField, isFalse);

    expect(config.onShowOnScreen, isNull);
    expect(config.onScrollDown, isNull);
    expect(config.onScrollUp, isNull);
    expect(config.onScrollLeft, isNull);
    expect(config.onScrollRight, isNull);
    expect(config.onLongPress, isNull);
    expect(config.onDecrease, isNull);
    expect(config.onIncrease, isNull);
    expect(config.onMoveCursorForwardByCharacter, isNull);
    expect(config.onMoveCursorBackwardByCharacter, isNull);
    expect(config.onTap, isNull);
477
    expect(config.customSemanticsActions[customAction], isNull);
478 479 480 481

    config.isSemanticBoundary = true;
    config.isButton = true;
    config.isMergingSemanticsOfDescendants = true;
482
    config.isEnabled = true;
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
    config.isChecked = true;
    config.isSelected = true;
    config.isBlockingSemanticsOfPreviouslyPaintedNodes = true;
    config.isFocused = true;
    config.isTextField = true;

    final VoidCallback onShowOnScreen = () { };
    final VoidCallback onScrollDown = () { };
    final VoidCallback onScrollUp = () { };
    final VoidCallback onScrollLeft = () { };
    final VoidCallback onScrollRight = () { };
    final VoidCallback onLongPress = () { };
    final VoidCallback onDecrease = () { };
    final VoidCallback onIncrease = () { };
    final MoveCursorHandler onMoveCursorForwardByCharacter = (bool _) { };
    final MoveCursorHandler onMoveCursorBackwardByCharacter = (bool _) { };
    final VoidCallback onTap = () { };
500
    final VoidCallback onCustomAction = () {};
501 502 503 504 505 506 507 508 509 510 511 512

    config.onShowOnScreen = onShowOnScreen;
    config.onScrollDown = onScrollDown;
    config.onScrollUp = onScrollUp;
    config.onScrollLeft = onScrollLeft;
    config.onScrollRight = onScrollRight;
    config.onLongPress = onLongPress;
    config.onDecrease = onDecrease;
    config.onIncrease = onIncrease;
    config.onMoveCursorForwardByCharacter = onMoveCursorForwardByCharacter;
    config.onMoveCursorBackwardByCharacter = onMoveCursorBackwardByCharacter;
    config.onTap = onTap;
513
    config.customSemanticsActions[customAction] = onCustomAction;
514 515 516 517

    expect(config.isSemanticBoundary, isTrue);
    expect(config.isButton, isTrue);
    expect(config.isMergingSemanticsOfDescendants, isTrue);
518
    expect(config.isEnabled, isTrue);
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
    expect(config.isChecked, isTrue);
    expect(config.isSelected, isTrue);
    expect(config.isBlockingSemanticsOfPreviouslyPaintedNodes, isTrue);
    expect(config.isFocused, isTrue);
    expect(config.isTextField, isTrue);

    expect(config.onShowOnScreen, same(onShowOnScreen));
    expect(config.onScrollDown, same(onScrollDown));
    expect(config.onScrollUp, same(onScrollUp));
    expect(config.onScrollLeft, same(onScrollLeft));
    expect(config.onScrollRight, same(onScrollRight));
    expect(config.onLongPress, same(onLongPress));
    expect(config.onDecrease, same(onDecrease));
    expect(config.onIncrease, same(onIncrease));
    expect(config.onMoveCursorForwardByCharacter, same(onMoveCursorForwardByCharacter));
    expect(config.onMoveCursorBackwardByCharacter, same(onMoveCursorBackwardByCharacter));
    expect(config.onTap, same(onTap));
536
    expect(config.customSemanticsActions[customAction], same(onCustomAction));
537
  });
538 539 540 541
}

class TestRender extends RenderProxyBox {

542
  TestRender({
543 544 545 546 547 548
    this.hasTapAction = false,
    this.hasLongPressAction = false,
    this.hasScrollLeftAction = false,
    this.hasScrollRightAction = false,
    this.hasScrollUpAction = false,
    this.hasScrollDownAction = false,
549 550 551 552 553 554 555 556 557 558 559
    this.isSemanticBoundary,
    RenderObject child
  }) : super(child);

  bool hasTapAction;
  bool hasLongPressAction;
  bool hasScrollLeftAction;
  bool hasScrollRightAction;
  bool hasScrollUpAction;
  bool hasScrollDownAction;
  bool isSemanticBoundary;
560 561 562


  @override
563 564
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);
565

566 567 568 569 570 571 572 573 574 575 576 577 578
    config.isSemanticBoundary = isSemanticBoundary;
    if (hasTapAction)
      config.onTap = () { };
    if (hasLongPressAction)
      config.onLongPress = () { };
    if (hasScrollLeftAction)
      config.onScrollLeft = () { };
    if (hasScrollRightAction)
      config.onScrollRight = () { };
    if (hasScrollUpAction)
      config.onScrollUp = () { };
    if (hasScrollDownAction)
      config.onScrollDown = () { };
579
  }
580
}
581 582 583

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