// Copyright 2015 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 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/semantics.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'semantics_tester.dart'; void main() { setUp(() { debugResetSemanticsIdCounter(); }); testWidgets('Semantics shutdown and restart', (WidgetTester tester) async { SemanticsTester semantics = new SemanticsTester(tester); final TestSemantics expectedSemantics = new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( label: 'test1', textDirection: TextDirection.ltr, ) ], ); await tester.pumpWidget( new Container( child: new Semantics( label: 'test1', textDirection: TextDirection.ltr, child: new Container() ) ) ); expect(semantics, hasSemantics( expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true, )); semantics.dispose(); semantics = null; expect(tester.binding.hasScheduledFrame, isFalse); semantics = new SemanticsTester(tester); expect(tester.binding.hasScheduledFrame, isTrue); await tester.pump(); expect(semantics, hasSemantics( expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true, )); semantics.dispose(); }); testWidgets('Detach and reattach assert', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); final GlobalKey key = new GlobalKey(); await tester.pumpWidget(new Directionality( textDirection: TextDirection.ltr, child: new Container( child: new Semantics( label: 'test1', child: new Semantics( key: key, container: true, label: 'test2a', child: new Container() ) ) ) )); expect(semantics, hasSemantics( new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( label: 'test1', children: <TestSemantics>[ new TestSemantics( label: 'test2a', ) ] ) ] ), ignoreId: true, ignoreRect: true, ignoreTransform: true, )); await tester.pumpWidget(new Directionality( textDirection: TextDirection.ltr, child: new Container( child: new Semantics( label: 'test1', child: new Semantics( container: true, label: 'middle', child: new Semantics( key: key, container: true, label: 'test2b', child: new Container() ) ) ) ) )); expect(semantics, hasSemantics( new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( label: 'test1', children: <TestSemantics>[ new TestSemantics( label: 'middle', children: <TestSemantics>[ new TestSemantics( label: 'test2b', ), ], ) ] ) ] ), ignoreId: true, ignoreRect: true, ignoreTransform: true, )); semantics.dispose(); }); testWidgets('Semantics and Directionality - RTL', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); await tester.pumpWidget( new Directionality( textDirection: TextDirection.rtl, child: new Semantics( label: 'test1', child: new Container(), ), ), ); expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.rtl)); }); testWidgets('Semantics and Directionality - LTR', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Semantics( label: 'test1', child: new Container(), ), ), ); expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.ltr)); }); testWidgets('Semantics and Directionality - cannot override RTL with LTR', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); final TestSemantics expectedSemantics = new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( label: 'test1', textDirection: TextDirection.ltr, ) ] ); await tester.pumpWidget( new Directionality( textDirection: TextDirection.rtl, child: new Semantics( label: 'test1', textDirection: TextDirection.ltr, child: new Container(), ), ), ); expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true)); }); testWidgets('Semantics and Directionality - cannot override LTR with RTL', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); final TestSemantics expectedSemantics = new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( label: 'test1', textDirection: TextDirection.rtl, ) ] ); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Semantics( label: 'test1', textDirection: TextDirection.rtl, child: new Container(), ), ), ); expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true)); }); testWidgets('Semantics label and hint', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Semantics( label: 'label', hint: 'hint', value: 'value', child: new Container(), ), ), ); final TestSemantics expectedSemantics = new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( label: 'label', hint: 'hint', value: 'value', textDirection: TextDirection.ltr, ) ] ); expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true)); }); testWidgets('Semantics hints can merge', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Semantics( container: true, child: new Column( children: <Widget>[ new Semantics( hint: 'hint one', ), new Semantics( hint: 'hint two', ) ], ), ), ), ); final TestSemantics expectedSemantics = new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( hint: 'hint one\nhint two', textDirection: TextDirection.ltr, ) ] ); expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true)); }); testWidgets('Semantics values do not merge', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Semantics( container: true, child: new Column( children: <Widget>[ new Semantics( value: 'value one', child: new Container( height: 10.0, width: 10.0, ) ), new Semantics( value: 'value two', child: new Container( height: 10.0, width: 10.0, ) ) ], ), ), ), ); final TestSemantics expectedSemantics = new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( children: <TestSemantics>[ new TestSemantics( value: 'value one', textDirection: TextDirection.ltr, ), new TestSemantics( value: 'value two', textDirection: TextDirection.ltr, ), ] ) ], ); expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true)); }); testWidgets('Semantics value and hint can merge', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Semantics( container: true, child: new Column( children: <Widget>[ new Semantics( hint: 'hint', ), new Semantics( value: 'value', ), ], ), ), ), ); final TestSemantics expectedSemantics = new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( hint: 'hint', value: 'value', textDirection: TextDirection.ltr, ) ] ); expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true)); }); testWidgets('Semantics widget supports all actions', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); final List<SemanticsAction> performedActions = <SemanticsAction>[]; await tester.pumpWidget( new Semantics( container: true, 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), onCopy: () => performedActions.add(SemanticsAction.copy), onCut: () => performedActions.add(SemanticsAction.cut), onPaste: () => performedActions.add(SemanticsAction.paste), onMoveCursorForwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorForwardByCharacter), onMoveCursorBackwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorBackwardByCharacter), onSetSelection: (TextSelection _) => performedActions.add(SemanticsAction.setSelection), onDidGainAccessibilityFocus: () => performedActions.add(SemanticsAction.didGainAccessibilityFocus), onDidLoseAccessibilityFocus: () => performedActions.add(SemanticsAction.didLoseAccessibilityFocus), ) ); final Set<SemanticsAction> allActions = SemanticsAction.values.values.toSet() ..remove(SemanticsAction.showOnScreen); // showOnScreen is non user-exposed. const int expectedId = 2; final TestSemantics expectedSemantics = new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( id: expectedId, rect: TestSemantics.fullScreen, actions: allActions.fold(0, (int previous, SemanticsAction action) => previous | action.index), previousNodeId: -1, nextNodeId: -1, ), ], ); expect(semantics, hasSemantics(expectedSemantics)); // Do the actions work? final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner; int expectedLength = 1; for (SemanticsAction action in allActions) { switch (action) { case SemanticsAction.moveCursorBackwardByCharacter: case SemanticsAction.moveCursorForwardByCharacter: semanticsOwner.performAction(expectedId, action, true); break; case SemanticsAction.setSelection: semanticsOwner.performAction(expectedId, action, <String, int>{ 'base': 4, 'extent': 5, }); break; default: semanticsOwner.performAction(expectedId, action); } expect(performedActions.length, expectedLength); expect(performedActions.last, action); expectedLength += 1; } semantics.dispose(); }); testWidgets('Actions can be replaced without triggering semantics update', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); int semanticsUpdateCount = 0; tester.binding.pipelineOwner.ensureSemantics( listener: () { semanticsUpdateCount += 1; } ); final List<String> performedActions = <String>[]; await tester.pumpWidget( new Semantics( container: true, onTap: () => performedActions.add('first'), ), ); const int expectedId = 2; final TestSemantics expectedSemantics = new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( id: expectedId, rect: TestSemantics.fullScreen, actions: SemanticsAction.tap.index, ), ], ); final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner; 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( new Semantics( 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( new Semantics( container: true, onTap: () => performedActions.add('second'), onLongPress: () => performedActions.add('longPress'), ), ); final TestSemantics expectedSemanticsWithLongPress = new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( 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( new Semantics( container: true, onTap: () => performedActions.add('second'), ), ); expect(semantics, hasSemantics(expectedSemantics)); expect(semanticsUpdateCount, 1); semantics.dispose(); }); testWidgets('Increased/decreased values are annotated', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Semantics( container: true, value: '10s', increasedValue: '11s', decreasedValue: '9s', onIncrease: () => () {}, onDecrease: () => () {}, ), ), ); expect(semantics, hasSemantics(new TestSemantics.root( children: <TestSemantics>[ new TestSemantics.rootChild( actions: SemanticsAction.increase.index | SemanticsAction.decrease.index, textDirection: TextDirection.ltr, value: '10s', increasedValue: '11s', decreasedValue: '9s', ), ], ), ignoreTransform: true, ignoreRect: true, ignoreId: true)); semantics.dispose(); }); testWidgets('Semantics widgets built in a widget tree are sorted properly', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); int semanticsUpdateCount = 0; tester.binding.pipelineOwner.ensureSemantics( listener: () { semanticsUpdateCount += 1; } ); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Semantics( sortKey: const CustomSortKey(0.0), explicitChildNodes: true, child: new Column( children: <Widget>[ new Semantics(sortKey: const CustomSortKey(3.0), child: const Text('Label 1')), new Semantics(sortKey: const CustomSortKey(2.0), child: const Text('Label 2')), new Semantics( sortKey: const CustomSortKey(1.0), explicitChildNodes: true, child: new Row( children: <Widget>[ new Semantics(sortKey: const OrdinalSortKey(3.0), child: const Text('Label 3')), new Semantics(sortKey: const OrdinalSortKey(2.0), child: const Text('Label 4')), new Semantics(sortKey: const OrdinalSortKey(1.0), child: const Text('Label 5')), ], ), ), ], ), ), ), ); expect(semanticsUpdateCount, 1); expect(semantics, hasSemantics( new TestSemantics( id: 0, children: <TestSemantics>[ new TestSemantics( id: 2, nextNodeId: 5, previousNodeId: -1, children: <TestSemantics>[ new TestSemantics( id: 3, label: r'Label 1', textDirection: TextDirection.ltr, nextNodeId: -1, previousNodeId: 4, ), new TestSemantics( id: 4, label: r'Label 2', textDirection: TextDirection.ltr, nextNodeId: 3, previousNodeId: 6, ), new TestSemantics( id: 5, nextNodeId: 8, previousNodeId: 2, children: <TestSemantics>[ new TestSemantics( id: 6, label: r'Label 3', textDirection: TextDirection.ltr, nextNodeId: 4, previousNodeId: 7, ), new TestSemantics( id: 7, label: r'Label 4', textDirection: TextDirection.ltr, nextNodeId: 6, previousNodeId: 8, ), new TestSemantics( id: 8, label: r'Label 5', textDirection: TextDirection.ltr, nextNodeId: 7, previousNodeId: 5, ), ], ), ], ), ], ), ignoreTransform: true, ignoreRect: true), ); semantics.dispose(); }); testWidgets('Semantics widgets built with explicit sort orders are sorted properly', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); int semanticsUpdateCount = 0; tester.binding.pipelineOwner.ensureSemantics( listener: () { semanticsUpdateCount += 1; } ); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Column( children: <Widget>[ new Semantics( sortOrder: new SemanticsSortOrder( keys: <SemanticsSortKey>[const CustomSortKey(3.0), const OrdinalSortKey(5.0)], ), child: const Text('Label 1'), ), new Semantics( sortOrder: new SemanticsSortOrder( keys: <SemanticsSortKey>[const CustomSortKey(2.0), const OrdinalSortKey(4.0)], ), child: const Text('Label 2'), ), new Row( children: <Widget>[ new Semantics( sortOrder: new SemanticsSortOrder( keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(3.0)], ), child: const Text('Label 3'), ), new Semantics( sortOrder: new SemanticsSortOrder( keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(2.0)], ), child: const Text('Label 4'), ), new Semantics( sortOrder: new SemanticsSortOrder( keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(1.0)], ), child: const Text('Label 5'), ), ], ), ], ), ), ); expect(semanticsUpdateCount, 1); expect(semantics, hasSemantics( new TestSemantics( children: <TestSemantics>[ new TestSemantics( label: r'Label 1', textDirection: TextDirection.ltr, nextNodeId: -1, previousNodeId: 3, ), new TestSemantics( label: r'Label 2', textDirection: TextDirection.ltr, nextNodeId: 2, previousNodeId: 4, ), new TestSemantics( label: r'Label 3', textDirection: TextDirection.ltr, nextNodeId: 3, previousNodeId: 5, ), new TestSemantics( label: r'Label 4', textDirection: TextDirection.ltr, nextNodeId: 4, previousNodeId: 6, ), new TestSemantics( label: r'Label 5', textDirection: TextDirection.ltr, nextNodeId: 5, previousNodeId: -1, ), ], ), ignoreTransform: true, ignoreRect: true, ignoreId: true)); semantics.dispose(); }); testWidgets('Semantics widgets built with some discarded sort orders are sorted properly', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); int semanticsUpdateCount = 0; tester.binding.pipelineOwner.ensureSemantics( listener: () { semanticsUpdateCount += 1; } ); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Semantics( sortKey: const OrdinalSortKey(0.0), explicitChildNodes: true, child: new Column( children: <Widget>[ new Semantics( sortOrder: new SemanticsSortOrder( keys: <SemanticsSortKey>[const CustomSortKey(3.0), const OrdinalSortKey(5.0)], discardParentOrder: true, // Replace this one. ), child: const Text('Label 1'), ), new Semantics( sortOrder: new SemanticsSortOrder( keys: <SemanticsSortKey>[const CustomSortKey(2.0), const OrdinalSortKey(4.0)], ), child: const Text('Label 2'), ), new Row( children: <Widget>[ new Semantics( sortOrder: new SemanticsSortOrder( keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(3.0)], discardParentOrder: true, // Replace this one. ), child: const Text('Label 3'), ), new Semantics( sortOrder: new SemanticsSortOrder( keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(2.0)], ), child: const Text('Label 4'), ), new Semantics( sortOrder: new SemanticsSortOrder( keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(1.0)], ), child: const Text('Label 5'), ), ], ), ], ), ), ), ); expect(semanticsUpdateCount, 1); expect(semantics, hasSemantics( new TestSemantics( children: <TestSemantics>[ new TestSemantics( nextNodeId: 5, previousNodeId: -1, children: <TestSemantics>[ new TestSemantics( label: r'Label 1', textDirection: TextDirection.ltr, nextNodeId: 7, previousNodeId: 5, ), new TestSemantics( label: r'Label 2', textDirection: TextDirection.ltr, nextNodeId: -1, previousNodeId: 6, ), new TestSemantics( label: r'Label 3', textDirection: TextDirection.ltr, nextNodeId: 3, previousNodeId: 2, ), new TestSemantics( label: r'Label 4', textDirection: TextDirection.ltr, nextNodeId: 4, previousNodeId: 7, ), new TestSemantics( label: r'Label 5', textDirection: TextDirection.ltr, nextNodeId: 6, previousNodeId: 3, ), ], ), ], ), ignoreTransform: true, ignoreRect: true, ignoreId: true), ); semantics.dispose(); }); testWidgets('Semantics widgets without sort orders are sorted properly', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); int semanticsUpdateCount = 0; tester.binding.pipelineOwner.ensureSemantics( listener: () { semanticsUpdateCount += 1; } ); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Column( children: <Widget>[ const Text('Label 1'), const Text('Label 2'), new Row( children: <Widget>[ const Text('Label 3'), const Text('Label 4'), const Text('Label 5'), ], ), ], ), ), ); expect(semanticsUpdateCount, 1); expect(semantics, hasSemantics( new TestSemantics( children: <TestSemantics>[ new TestSemantics( label: r'Label 1', textDirection: TextDirection.ltr, previousNodeId: -1, ), new TestSemantics( label: r'Label 2', textDirection: TextDirection.ltr, previousNodeId: 2, ), new TestSemantics( label: r'Label 3', textDirection: TextDirection.ltr, previousNodeId: 3, ), new TestSemantics( label: r'Label 4', textDirection: TextDirection.ltr, previousNodeId: 4, ), new TestSemantics( label: r'Label 5', textDirection: TextDirection.ltr, previousNodeId: 5, ), ], ), ignoreTransform: true, ignoreRect: true, ignoreId: true), ); semantics.dispose(); }); testWidgets('Semantics widgets that are transformed are sorted properly', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); int semanticsUpdateCount = 0; tester.binding.pipelineOwner.ensureSemantics( listener: () { semanticsUpdateCount += 1; } ); await tester.pumpWidget( new Directionality( textDirection: TextDirection.ltr, child: new Column( children: <Widget>[ const Text('Label 1'), const Text('Label 2'), new Transform.rotate( angle: pi / 2.0, child: new Row( children: <Widget>[ const Text('Label 3'), const Text('Label 4'), const Text('Label 5'), ], ), ), ], ), ), ); expect(semanticsUpdateCount, 1); expect(semantics, hasSemantics( new TestSemantics( children: <TestSemantics>[ new TestSemantics( label: r'Label 1', textDirection: TextDirection.ltr, previousNodeId: 6, ), new TestSemantics( label: r'Label 2', textDirection: TextDirection.ltr, previousNodeId: 2, ), new TestSemantics( label: r'Label 3', textDirection: TextDirection.ltr, previousNodeId: -1, ), new TestSemantics( label: r'Label 4', textDirection: TextDirection.ltr, previousNodeId: 4, ), new TestSemantics( label: r'Label 5', textDirection: TextDirection.ltr, previousNodeId: 5, ), ], ), ignoreTransform: true, ignoreRect: true, ignoreId: true), ); semantics.dispose(); }); testWidgets( 'Semantics widgets without sort orders are sorted properly when no Directionality is present', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); int semanticsUpdateCount = 0; tester.binding.pipelineOwner.ensureSemantics(listener: () { semanticsUpdateCount += 1; }); await tester.pumpWidget( new Stack( 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]. new Semantics( button: true, child: const Placeholder(), ), new Positioned( top: 200.0, left: 100.0, child: new Semantics( // Box 0 button: true, child: const SizedBox(width: 30.0, height: 30.0), ), ), new Positioned( top: 100.0, left: 200.0, child: new Semantics( // Box 1 button: true, child: const SizedBox(width: 30.0, height: 30.0), ), ), new Positioned( top: 100.0, left: 100.0, child: new Semantics( // Box 2 button: true, child: const SizedBox(width: 30.0, height: 30.0), ), ), new Positioned( top: 100.0, left: 0.0, child: new Semantics( // Box 3 button: true, child: const SizedBox(width: 30.0, height: 30.0), ), ), new Positioned( top: 10.0, left: 100.0, child: new Semantics( // Box 4 button: true, child: const SizedBox(width: 30.0, height: 30.0), ), ), ], ), ); expect(semanticsUpdateCount, 1); expect( semantics, hasSemantics( new TestSemantics( children: <TestSemantics>[ new TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.isButton], previousNodeId: -1, ), new TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.isButton], previousNodeId: 6, ), new TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.isButton], previousNodeId: 7, ), new TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.isButton], previousNodeId: 4, ), new TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.isButton], previousNodeId: 5, ), new TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.isButton], previousNodeId: 2, ), ], ), ignoreTransform: true, ignoreRect: true, ignoreId: true), ); semantics.dispose(); }); } class CustomSortKey extends OrdinalSortKey { const CustomSortKey(double order, {String name}) : super(order, name: name); }