diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart index 947e455d5d..9153cc47b9 100644 --- a/packages/flutter/lib/src/material/button.dart +++ b/packages/flutter/lib/src/material/button.dart @@ -332,7 +332,7 @@ class _MaterialButtonState extends State { child: new Center( widthFactor: 1.0, heightFactor: 1.0, - child: new Semantics(button: true, child: widget.child), + child: widget.child, ) ) ) @@ -352,12 +352,17 @@ class _MaterialButtonState extends State { child: contents ); } - return new ConstrainedBox( - constraints: new BoxConstraints( - minWidth: widget.minWidth ?? buttonTheme.minWidth, - minHeight: height, + return new Semantics( + container: true, + button: true, + enabled: widget.enabled, + child: new ConstrainedBox( + constraints: new BoxConstraints( + minWidth: widget.minWidth ?? buttonTheme.minWidth, + minHeight: height, + ), + child: contents ), - child: contents ); } } diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 708bc4ecb4..326c260930 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -2243,7 +2243,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im // RenderObject are still up-to date. Therefore, we will later only rebuild // the semantics subtree starting at th identified semantics boundary. - final bool wasSemanticsBoundary = _cachedSemanticsConfiguration?.isSemanticBoundary == true; + final bool wasSemanticsBoundary = _semantics != null && _cachedSemanticsConfiguration?.isSemanticBoundary == true; _cachedSemanticsConfiguration = null; bool isEffectiveSemanticsBoundary = _semanticsConfiguration.isSemanticBoundary && wasSemanticsBoundary; RenderObject node = this; @@ -2254,7 +2254,6 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im node._needsSemanticsUpdate = true; node = node.parent; - node._cachedSemanticsConfiguration = null; isEffectiveSemanticsBoundary = node._semanticsConfiguration.isSemanticBoundary; if (isEffectiveSemanticsBoundary && node._semantics == null) { // We have reached a semantics boundary that doesn't own a semantics node. @@ -2274,10 +2273,6 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im owner._nodesNeedingSemantics.remove(this); } if (!node._needsSemanticsUpdate) { - if (node != this) { - // Reset for `this` happened above already. - node._cachedSemanticsConfiguration = null; - } node._needsSemanticsUpdate = true; if (owner != null) { assert(node._semanticsConfiguration.isSemanticBoundary || node.parent is! RenderObject); diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 79e9e2294f..143dcd86b5 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -2565,37 +2565,6 @@ class RenderSemanticsGestureHandler extends RenderProxyBox { _onVerticalDragUpdate = onVerticalDragUpdate, super(child); - /// When a [SemanticsNode] that is a direct child of this object's - /// [SemanticsNode] is tagged with [excludeFromScrolling] it will not be - /// part of the scrolling area for semantic purposes. - /// - /// This behavior is only active if the [SemanticsNode] of this - /// [RenderSemanticsGestureHandler] is tagged with [useTwoPaneSemantics]. - /// Otherwise, the [excludeFromScrolling] tag is ignored. - /// - /// As an example, a [RenderSliver] that stays on the screen within a - /// [Scrollable] even though the user has scrolled past it (e.g. a pinned app - /// bar) can tag its [SemanticsNode] with [excludeFromScrolling] to indicate - /// that it should no longer be considered for semantic actions related to - /// scrolling. - static const SemanticsTag excludeFromScrolling = const SemanticsTag('RenderSemanticsGestureHandler.excludeFromScrolling'); - - /// If the [SemanticsNode] of this [RenderSemanticsGestureHandler] is tagged - /// with [useTwoPaneSemantics], two semantics nodes will be used to represent - /// this render object in the semantics tree. - /// - /// Two semantics nodes are necessary to exclude certain child nodes (via the - /// [excludeFromScrolling] tag) from the scrollable area for semantic - /// purposes. - /// - /// If this tag is used, the first "outer" semantics node is the regular node - /// of this object. The second "inner" node is introduced as a child to that - /// node. All scrollable children become children of the inner node, which has - /// the semantic scrolling logic enabled. All children that have been - /// excluded from scrolling with [excludeFromScrolling] are turned into - /// children of the outer node. - static const SemanticsTag useTwoPaneSemantics = const SemanticsTag('RenderSemanticsGestureHandler.twoPane'); - /// If non-null, the set of actions to allow. Other actions will be omitted, /// even if their callback is provided. /// @@ -2673,24 +2642,10 @@ class RenderSemanticsGestureHandler extends RenderProxyBox { /// leftwards drag. double scrollFactor; - bool get _hasHandlers { - return onTap != null - || onLongPress != null - || onHorizontalDragUpdate != null - || onVerticalDragUpdate != null; - } - @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); - config.isSemanticBoundary = _hasHandlers; - - // TODO(goderbauer): this needs to be set even when there is only potential - // for this to become a scroll view. - config.explicitChildNodes = onHorizontalDragUpdate != null - || onVerticalDragUpdate != null; - if (onTap != null && _isValidAction(SemanticsAction.tap)) config.onTap = onTap; if (onLongPress != null && _isValidAction(SemanticsAction.longPress)) @@ -2713,42 +2668,6 @@ class RenderSemanticsGestureHandler extends RenderProxyBox { return validActions == null || validActions.contains(action); } - SemanticsNode _innerNode; - SemanticsNode _annotatedNode; - - /// Sends a [SemanticsEvent] in the context of the [SemanticsNode] that is - /// annotated with this object's semantics information. - void sendSemanticsEvent(SemanticsEvent event) { - _annotatedNode?.sendEvent(event); - } - - @override - void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable children) { - if (children.isEmpty || !children.first.isTagged(useTwoPaneSemantics)) { - _annotatedNode = node; - super.assembleSemanticsNode(node, config, children); - return; - } - - _innerNode ??= new SemanticsNode(showOnScreen: showOnScreen); - _innerNode - ..isMergedIntoParent = node.isPartOfNodeMerging - ..rect = Offset.zero & node.rect.size; - _annotatedNode = _innerNode; - - final List excluded = [_innerNode]; - final List included = []; - for (SemanticsNode child in children) { - assert(child.isTagged(useTwoPaneSemantics)); - if (child.isTagged(excludeFromScrolling)) - excluded.add(child); - else - included.add(child); - } - node.updateWith(config: null, childrenInInversePaintOrder: excluded); - _innerNode.updateWith(config: config, childrenInInversePaintOrder: included); - } - void _performSemanticScrollLeft() { if (onHorizontalDragUpdate != null) { final double primaryDelta = size.width * -scrollFactor; diff --git a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart index 435690efeb..a831d8e473 100644 --- a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart @@ -14,8 +14,8 @@ import 'package:vector_math/vector_math_64.dart'; import 'binding.dart'; import 'box.dart'; import 'object.dart'; -import 'proxy_box.dart'; import 'sliver.dart'; +import 'viewport.dart'; import 'viewport_offset.dart'; /// A base class for slivers that have a [RenderBox] child which scrolls @@ -225,7 +225,7 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje super.describeSemanticsConfiguration(config); if (_excludeFromSemanticsScrolling) - config.addTagForChildren(RenderSemanticsGestureHandler.excludeFromScrolling); + config.addTagForChildren(RenderViewport.excludeFromScrolling); } @override diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index a42687b453..8e4cebd54b 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -12,7 +12,6 @@ import 'package:vector_math/vector_math_64.dart'; import 'binding.dart'; import 'box.dart'; import 'object.dart'; -import 'proxy_box.dart'; import 'sliver.dart'; import 'viewport_offset.dart'; @@ -91,12 +90,11 @@ abstract class RenderViewportBase node.isMergedIntoParent) || isPartOfNodeMerging); _debugPreviousSnapshot = new List.from(newChildren); @@ -677,7 +678,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { } if (newChildren != null) { for (SemanticsNode child in newChildren) { - assert(!child.isInvisible, 'Child with id ${child.id} is invisible and should not be added to tree.'); + assert(!child.isInvisible, '$child is invisible and should not be added as child of $this.'); child._dead = false; } } @@ -1072,7 +1073,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { hideOwner = inDirtyNodes; } properties.add(new DiagnosticsProperty('owner', owner, level: hideOwner ? DiagnosticLevel.hidden : DiagnosticLevel.info)); - properties.add(new FlagProperty('isPartOfNodeMerging', value: isPartOfNodeMerging, ifTrue: 'leaf merge')); + properties.add(new FlagProperty('isMergedIntoParent', value: isMergedIntoParent, ifTrue: 'merged up ⬆️')); + properties.add(new FlagProperty('mergeAllDescendantsIntoThisNode', value: mergeAllDescendantsIntoThisNode, ifTrue: 'merge boundary ⛔️')); final Offset offset = transform != null ? MatrixUtils.getAsTranslation(transform) : null; if (offset != null) { properties.add(new DiagnosticsProperty('rect', rect.shift(offset), showName: false)); @@ -1344,7 +1346,7 @@ class SemanticsConfiguration { bool get isSemanticBoundary => _isSemanticBoundary; bool _isSemanticBoundary = false; set isSemanticBoundary(bool value) { - assert(!isMergingDescendantsIntoOneNode || value); + assert(!isMergingSemanticsOfDescendants || value); _isSemanticBoundary = value; } @@ -1380,20 +1382,6 @@ class SemanticsConfiguration { /// determine if a node is previous to this one. bool isBlockingSemanticsOfPreviouslyPaintedNodes = false; - /// Whether the semantics information of all descendants should be merged - /// into the owning [RenderObject] semantics node. - /// - /// When this is set to true the [SemanticsNode] of the owning [RenderObject] - /// will not have any children. - /// - /// Setting this to true requires that [isSemanticBoundary] is also true. - bool get isMergingDescendantsIntoOneNode => _isMergingDescendantsIntoOneNode; - bool _isMergingDescendantsIntoOneNode = false; - set isMergingDescendantsIntoOneNode(bool value) { - assert(isSemanticBoundary); - _isMergingDescendantsIntoOneNode = value; - } - // SEMANTIC ANNOTATIONS // These will end up on [SemanticNode]s generated from // [SemanticsConfiguration]s. @@ -1645,9 +1633,12 @@ class SemanticsConfiguration { /// If set to true, the descendants of the owning [RenderObject]'s /// [SemanticsNode] will merge their semantic information into the /// [SemanticsNode] representing the owning [RenderObject]. + /// + /// Setting this to true requires that [isSemanticBoundary] is also true. bool get isMergingSemanticsOfDescendants => _isMergingSemanticsOfDescendants; bool _isMergingSemanticsOfDescendants = false; set isMergingSemanticsOfDescendants(bool value) { + assert(isSemanticBoundary); _isMergingSemanticsOfDescendants = value; _hasBeenAnnotated = true; } @@ -1910,9 +1901,11 @@ class SemanticsConfiguration { /// Returns an exact copy of this configuration. SemanticsConfiguration copy() { return new SemanticsConfiguration() - ..isSemanticBoundary = isSemanticBoundary + .._isSemanticBoundary = _isSemanticBoundary ..explicitChildNodes = explicitChildNodes + ..isBlockingSemanticsOfPreviouslyPaintedNodes = isBlockingSemanticsOfPreviouslyPaintedNodes .._hasBeenAnnotated = _hasBeenAnnotated + .._isMergingSemanticsOfDescendants = _isMergingSemanticsOfDescendants .._textDirection = _textDirection .._label = _label .._increasedValue = _increasedValue @@ -1920,6 +1913,7 @@ class SemanticsConfiguration { .._decreasedValue = _decreasedValue .._hint = _hint .._flags = _flags + .._tagsForChildren = _tagsForChildren .._actionsAsBits = _actionsAsBits .._actions.addAll(_actions); } diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index fcc030034d..ab2f518b7e 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -570,21 +570,6 @@ class RawGestureDetectorState extends State { } } - /// Sends a [SemanticsEvent] in the context of the [SemanticsNode] that is - /// annotated with this object's semantics information. - /// - /// The event can be interpreted by assistive technologies to provide - /// additional feedback to the user about the state of the UI. - /// - /// The event will not be sent if [RawGestureDetector.excludeFromSemantics] is - /// set to true. - void sendSemanticsEvent(SemanticsEvent event) { - if (!widget.excludeFromSemantics) { - final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject(); - semanticsGestureHandler.sendSemanticsEvent(event); - } - } - @override void dispose() { for (GestureRecognizer recognizer in _recognizers.values) diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 221b87f561..8ca7830617 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -281,7 +281,8 @@ class ScrollableState extends State with TickerProviderStateMixin if (_semanticsScrollEventScheduled) return; SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) { - _gestureDetectorKey.currentState?.sendSemanticsEvent(new ScrollCompletedSemanticsEvent( + final _RenderExcludableScrollSemantics render = _excludableScrollSemanticsKey.currentContext?.findRenderObject(); + render?.sendSemanticsEvent(new ScrollCompletedSemanticsEvent( axis: position.axis, pixels: position.pixels, minScrollExtent: position.minScrollExtent, @@ -332,7 +333,9 @@ class ScrollableState extends State with TickerProviderStateMixin } - // SEMANTICS ACTIONS + // SEMANTICS + + final GlobalKey _excludableScrollSemanticsKey = new GlobalKey(); @override @protected @@ -487,18 +490,24 @@ class ScrollableState extends State with TickerProviderStateMixin Widget build(BuildContext context) { assert(position != null); // TODO(ianh): Having all these global keys is sad. - final Widget result = new RawGestureDetector( - key: _gestureDetectorKey, - gestures: _gestureRecognizers, - behavior: HitTestBehavior.opaque, - child: new IgnorePointer( - key: _ignorePointerKey, - ignoring: _shouldIgnorePointer, - ignoringSemantics: false, - child: new _ScrollableScope( - scrollable: this, - position: position, - child: widget.viewportBuilder(context, position), + final Widget result = new _ExcludableScrollSemantics( + key: _excludableScrollSemanticsKey, + child: new RawGestureDetector( + key: _gestureDetectorKey, + gestures: _gestureRecognizers, + behavior: HitTestBehavior.opaque, + child: new Semantics( + explicitChildNodes: true, + child: new IgnorePointer( + key: _ignorePointerKey, + ignoring: _shouldIgnorePointer, + ignoringSemantics: false, + child: new _ScrollableScope( + scrollable: this, + position: position, + child: widget.viewportBuilder(context, position), + ), + ), ), ), ); @@ -511,3 +520,70 @@ class ScrollableState extends State with TickerProviderStateMixin description.add(new DiagnosticsProperty('position', position)); } } + +/// With [_ExcludableScrollSemantics] certain child [SemanticsNode]s can be +/// excluded from the scrollable area for semantics purposes. +/// +/// Nodes, that are to be excluded, have to be tagged with +/// [RenderViewport.excludeFromScrolling] and the [RenderAbstractViewport] in +/// use has to add the [RenderViewport.useTwoPaneSemantics] tag to its +/// [SemanticsConfiguration] by overriding +/// [RenderObject.describeSemanticsConfiguration]. +/// +/// If the tag [RenderViewport.useTwoPaneSemantics] is present on the viewport, +/// two semantics nodes will be used to represent the [Scrollable]: The outer +/// node will contain all children, that are excluded from scrolling. The inner +/// node, which is annotated with the scrolling actions, will house the +/// scrollable children. +class _ExcludableScrollSemantics extends SingleChildRenderObjectWidget { + const _ExcludableScrollSemantics({ Key key, Widget child }) : super(key: key, child: child); + + @override + _RenderExcludableScrollSemantics createRenderObject(BuildContext context) => new _RenderExcludableScrollSemantics(); +} + +class _RenderExcludableScrollSemantics extends RenderProxyBox { + _RenderExcludableScrollSemantics({ RenderBox child }) : super(child); + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.isSemanticBoundary = true; + } + + SemanticsNode _innerNode; + SemanticsNode _annotatedNode; + + @override + void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable children) { + if (children.isEmpty || !children.first.isTagged(RenderViewport.useTwoPaneSemantics)) { + _annotatedNode = node; + super.assembleSemanticsNode(node, config, children); + return; + } + + _innerNode ??= new SemanticsNode(showOnScreen: showOnScreen); + _innerNode + ..isMergedIntoParent = node.isPartOfNodeMerging + ..rect = Offset.zero & node.rect.size; + _annotatedNode = _innerNode; + + final List excluded = [_innerNode]; + final List included = []; + for (SemanticsNode child in children) { + assert(child.isTagged(RenderViewport.useTwoPaneSemantics)); + if (child.isTagged(RenderViewport.excludeFromScrolling)) + excluded.add(child); + else + included.add(child); + } + node.updateWith(config: null, childrenInInversePaintOrder: excluded); + _innerNode.updateWith(config: config, childrenInInversePaintOrder: included); + } + + /// Sends a [SemanticsEvent] in the context of the [SemanticsNode] that is + /// annotated with this object's semantics information. + void sendSemanticsEvent(SemanticsEvent event) { + _annotatedNode?.sendEvent(event); + } +} diff --git a/packages/flutter/test/material/buttons_test.dart b/packages/flutter/test/material/buttons_test.dart index 8f0e7f8022..1e15978fa2 100644 --- a/packages/flutter/test/material/buttons_test.dart +++ b/packages/flutter/test/material/buttons_test.dart @@ -13,6 +13,10 @@ import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; void main() { + setUp(() { + debugResetSemanticsIdCounter(); + }); + testWidgets('Does FlatButton contribute semantics', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); await tester.pumpWidget( @@ -33,11 +37,17 @@ void main() { new TestSemantics.root( children: [ new TestSemantics.rootChild( - actions: SemanticsAction.tap.index, + actions: [ + SemanticsAction.tap, + ], label: 'ABC', rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 36.0), transform: new Matrix4.translationValues(356.0, 282.0, 0.0), - flags: SemanticsFlag.isButton.index, + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], ) ], ), @@ -67,11 +77,17 @@ void main() { new TestSemantics.root( children: [ new TestSemantics.rootChild( - actions: SemanticsAction.tap.index, + actions: [ + SemanticsAction.tap, + ], label: 'ABC', rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 36.0), transform: new Matrix4.translationValues(356.0, 282.0, 0.0), - flags: SemanticsFlag.isButton.index, + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], ) ] ), @@ -248,4 +264,83 @@ void main() { await gesture.up(); }); + testWidgets('Disabled MaterialButton has same semantic size as enabled and exposes disabled semantics', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + final Rect expectedButtonSize = new Rect.fromLTRB(0.0, 0.0, 116.0, 36.0); + // Button is in center of screen + final Matrix4 expectedButtonTransform = new Matrix4.identity() + ..translate( + TestSemantics.fullScreen.width / 2 - expectedButtonSize.width /2, + TestSemantics.fullScreen.height / 2 - expectedButtonSize.height /2, + ); + + // enabled button + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Material( + child: new Center( + child: new MaterialButton( + child: const Text('Button'), + onPressed: () { /* to make sure the button is enabled */ }, + ), + ), + ), + )); + + expect(semantics, hasSemantics( + new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + rect: expectedButtonSize, + transform: expectedButtonTransform, + label: 'Button', + actions: [ + SemanticsAction.tap, + ], + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], + ), + ], + ), + )); + + // disabled button + await tester.pumpWidget(const Directionality( + textDirection: TextDirection.ltr, + child: const Material( + child: const Center( + child: const MaterialButton( + child: const Text('Button'), + onPressed: null, // button is disabled + ), + ), + ), + )); + + expect(semantics, hasSemantics( + new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + rect: expectedButtonSize, + transform: expectedButtonTransform, + label: 'Button', + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.hasEnabledState, + ], + ), + ], + ), + )); + + + semantics.dispose(); + }); + } diff --git a/packages/flutter/test/material/card_test.dart b/packages/flutter/test/material/card_test.dart index 8124f2d0f3..078d9a1d19 100644 --- a/packages/flutter/test/material/card_test.dart +++ b/packages/flutter/test/material/card_test.dart @@ -48,8 +48,14 @@ void main() { id: 2, label: 'Button', textDirection: TextDirection.ltr, - actions: SemanticsAction.tap.index, - flags: SemanticsFlag.isButton.index, + actions: [ + SemanticsAction.tap, + ], + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], ), ], ), diff --git a/packages/flutter/test/material/control_list_tile_test.dart b/packages/flutter/test/material/control_list_tile_test.dart index dfda3e8beb..16e93f60ca 100644 --- a/packages/flutter/test/material/control_list_tile_test.dart +++ b/packages/flutter/test/material/control_list_tile_test.dart @@ -94,11 +94,11 @@ void main() { ], ), )); + // This test verifies that the label and the control get merged. expect(semantics, hasSemantics(new TestSemantics.root( children: [ new TestSemantics.rootChild( - id: 1, rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0), transform: null, flags: [ @@ -111,7 +111,6 @@ void main() { label: 'aaa\nAAA', ), new TestSemantics.rootChild( - id: 4, rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0), transform: new Matrix4.translationValues(0.0, 56.0, 0.0), flags: [ @@ -124,7 +123,6 @@ void main() { label: 'bbb\nBBB', ), new TestSemantics.rootChild( - id: 7, rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0), transform: new Matrix4.translationValues(0.0, 112.0, 0.0), flags: [ @@ -136,7 +134,7 @@ void main() { label: 'CCC\nccc', ), ], - ))); + ), ignoreId: true)); }); } diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index f104bfa46e..7e7f9974d3 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -585,13 +585,21 @@ void _tests() { textDirection: TextDirection.ltr, ), new TestSemantics( - flags: [SemanticsFlag.isButton], + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], actions: [SemanticsAction.tap], label: r'CANCEL', textDirection: TextDirection.ltr, ), new TestSemantics( - flags: [SemanticsFlag.isButton], + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], actions: [SemanticsAction.tap], label: r'OK', textDirection: TextDirection.ltr, diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index b890e40402..4ce35d2bd8 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -165,6 +165,10 @@ class TestScrollPhysics extends ScrollPhysics { } void main() { + setUp(() { + debugResetSemanticsIdCounter(); + }); + testWidgets('TabBar tap selects tab', (WidgetTester tester) async { final List tabs = ['A', 'B', 'C']; @@ -1213,19 +1217,25 @@ void main() { children: [ new TestSemantics( id: 2, - actions: SemanticsAction.tap.index, - flags: SemanticsFlag.isSelected.index, - label: 'TAB #0\nTab 1 of 2', - rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight), - transform: new Matrix4.translationValues(0.0, 276.0, 0.0), - ), - new TestSemantics( - id: 3, - actions: SemanticsAction.tap.index, - label: 'TAB #1\nTab 2 of 2', - rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight), - transform: new Matrix4.translationValues(108.0, 276.0, 0.0), - ), + rect: TestSemantics.fullScreen, + children: [ + new TestSemantics( + id: 3, + actions: SemanticsAction.tap.index, + flags: SemanticsFlag.isSelected.index, + label: 'TAB #0\nTab 1 of 2', + rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight), + transform: new Matrix4.translationValues(0.0, 276.0, 0.0), + ), + new TestSemantics( + id: 4, + actions: SemanticsAction.tap.index, + label: 'TAB #1\nTab 2 of 2', + rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight), + transform: new Matrix4.translationValues(108.0, 276.0, 0.0), + ), + ] + ) ], ), ], @@ -1458,24 +1468,30 @@ void main() { final TestSemantics expectedSemantics = new TestSemantics.root( children: [ new TestSemantics.rootChild( - id: 23, + id: 1, rect: TestSemantics.fullScreen, children: [ new TestSemantics( - id: 24, - actions: SemanticsAction.tap.index, - flags: SemanticsFlag.isSelected.index, - label: 'Semantics override 0\nTab 1 of 2', - rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight), - transform: new Matrix4.translationValues(0.0, 276.0, 0.0), - ), - new TestSemantics( - id: 25, - actions: SemanticsAction.tap.index, - label: 'Semantics override 1\nTab 2 of 2', - rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight), - transform: new Matrix4.translationValues(108.0, 276.0, 0.0), - ), + id: 2, + rect: TestSemantics.fullScreen, + children: [ + new TestSemantics( + id: 3, + actions: SemanticsAction.tap.index, + flags: SemanticsFlag.isSelected.index, + label: 'Semantics override 0\nTab 1 of 2', + rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight), + transform: new Matrix4.translationValues(0.0, 276.0, 0.0), + ), + new TestSemantics( + id: 4, + actions: SemanticsAction.tap.index, + label: 'Semantics override 1\nTab 2 of 2', + rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight), + transform: new Matrix4.translationValues(108.0, 276.0, 0.0), + ), + ] + ) ], ), ], diff --git a/packages/flutter/test/semantics/semantics_test.dart b/packages/flutter/test/semantics/semantics_test.dart index bc2c160fcc..34ba425efa 100644 --- a/packages/flutter/test/semantics/semantics_test.dart +++ b/packages/flutter/test/semantics/semantics_test.dart @@ -11,6 +11,10 @@ import '../rendering/rendering_tester.dart'; void main() { + setUp(() { + debugResetSemanticsIdCounter(); + }); + group('SemanticsNode', () { const SemanticsTag tag1 = const SemanticsTag('Tag One'); const SemanticsTag tag2 = const SemanticsTag('Tag Two'); @@ -45,6 +49,7 @@ void main() { tags.add(tag3); final SemanticsConfiguration config = new SemanticsConfiguration() + ..isSemanticBoundary = true ..isMergingSemanticsOfDescendants = true; node.updateWith( @@ -121,9 +126,9 @@ void main() { expect( root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal), - 'SemanticsNode#8(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n' - '├SemanticsNode#6(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n' - '└SemanticsNode#7(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n', + 'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n' + '├SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n' + '└SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n', ); }); @@ -140,16 +145,16 @@ void main() { ); expect( root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal), - 'SemanticsNode#11(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n' - '├SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n' - '└SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n', + 'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n' + '├SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n' + '└SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n', ); expect( root.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest), - 'SemanticsNode#11(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n' - '├SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n' - '└SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n', + 'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n' + '├SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n' + '└SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n', ); final SemanticsNode child3 = new SemanticsNode() @@ -173,22 +178,22 @@ void main() { expect( rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal), - 'SemanticsNode#15(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n' - '├SemanticsNode#12(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n' - '│├SemanticsNode#14(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n' - '│└SemanticsNode#13(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n' - '├SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n' - '└SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n', + 'SemanticsNode#7(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n' + '├SemanticsNode#4(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n' + '│├SemanticsNode#6(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n' + '│└SemanticsNode#5(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n' + '├SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n' + '└SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n', ); expect( rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest), - 'SemanticsNode#15(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n' - '├SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n' - '├SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n' - '└SemanticsNode#12(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n' - ' ├SemanticsNode#13(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n' - ' └SemanticsNode#14(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n', + 'SemanticsNode#7(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n' + '├SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n' + '├SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n' + '└SemanticsNode#4(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n' + ' ├SemanticsNode#5(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n' + ' └SemanticsNode#6(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n', ); }); @@ -196,15 +201,16 @@ void main() { final SemanticsNode minimalProperties = new SemanticsNode(); expect( minimalProperties.toStringDeep(), - 'SemanticsNode#16(Rect.fromLTRB(0.0, 0.0, 0.0, 0.0))\n', + 'SemanticsNode#1(Rect.fromLTRB(0.0, 0.0, 0.0, 0.0))\n', ); expect( minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden), - 'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, isFocused: false, isButton: false, isTextField: false, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null)\n' + 'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, isFocused: false, isButton: false, isTextField: false, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null)\n' ); final SemanticsConfiguration config = new SemanticsConfiguration() + ..isSemanticBoundary = true ..isMergingSemanticsOfDescendants = true ..onScrollUp = () { } ..onLongPress = () { } @@ -220,7 +226,7 @@ void main() { ..updateWith(config: config, childrenInInversePaintOrder: null); expect( allProperties.toStringDeep(), - 'SemanticsNode#17(STALE, owner: null, leaf merge, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), actions: [longPress, scrollUp, showOnScreen], unchecked, selected, button, label: "Use all the properties", textDirection: rtl)\n', + 'SemanticsNode#2(STALE, owner: null, merge boundary ⛔️, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), actions: [longPress, scrollUp, showOnScreen], unchecked, selected, button, label: "Use all the properties", textDirection: rtl)\n', ); expect( allProperties.getSemanticsData().toString(), @@ -232,7 +238,7 @@ void main() { ..transform = new Matrix4.diagonal3(new Vector3(10.0, 10.0, 1.0)); expect( scaled.toStringDeep(), - 'SemanticsNode#18(STALE, owner: null, Rect.fromLTRB(50.0, 10.0, 70.0, 40.0) scaled by 10.0x)\n', + 'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(50.0, 10.0, 70.0, 40.0) scaled by 10.0x)\n', ); expect( scaled.getSemanticsData().toString(), @@ -251,7 +257,6 @@ void main() { expect(config.isSelected, isFalse); expect(config.isBlockingSemanticsOfPreviouslyPaintedNodes, isFalse); expect(config.isFocused, isFalse); - expect(config.isMergingDescendantsIntoOneNode, isFalse); expect(config.isTextField, isFalse); expect(config.onShowOnScreen, isNull); @@ -274,7 +279,6 @@ void main() { config.isSelected = true; config.isBlockingSemanticsOfPreviouslyPaintedNodes = true; config.isFocused = true; - config.isMergingDescendantsIntoOneNode = true; config.isTextField = true; final VoidCallback onShowOnScreen = () { }; @@ -309,7 +313,6 @@ void main() { expect(config.isSelected, isTrue); expect(config.isBlockingSemanticsOfPreviouslyPaintedNodes, isTrue); expect(config.isFocused, isTrue); - expect(config.isMergingDescendantsIntoOneNode, isTrue); expect(config.isTextField, isTrue); expect(config.onShowOnScreen, same(onShowOnScreen)); diff --git a/packages/flutter/test/widgets/keep_alive_test.dart b/packages/flutter/test/widgets/keep_alive_test.dart index 1c56651c67..fb4d8a1733 100644 --- a/packages/flutter/test/widgets/keep_alive_test.dart +++ b/packages/flutter/test/widgets/keep_alive_test.dart @@ -220,73 +220,84 @@ void main() { ' │ diagnosis: insufficient data to draw conclusion (less than five\n' ' │ repaints)\n' ' │\n' - ' └─child: RenderSemanticsGestureHandler#00000\n' + ' └─child: _RenderExcludableScrollSemantics#00000\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' + ' │ semantic boundary\n' ' │ size: Size(800.0, 600.0)\n' - ' │ gestures: vertical scroll\n' ' │\n' - ' └─child: RenderPointerListener#00000\n' + ' └─child: RenderSemanticsGestureHandler#00000\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ size: Size(800.0, 600.0)\n' - ' │ behavior: opaque\n' - ' │ listeners: down\n' + ' │ gestures: vertical scroll\n' ' │\n' - ' └─child: RenderIgnorePointer#00000\n' + ' └─child: RenderPointerListener#00000\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ size: Size(800.0, 600.0)\n' - ' │ ignoring: false\n' - ' │ ignoringSemantics: false\n' + ' │ behavior: opaque\n' + ' │ listeners: down\n' ' │\n' - ' └─child: RenderViewport#00000\n' + ' └─child: RenderSemanticsAnnotations#00000\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' - ' │ layer: OffsetLayer#00000\n' ' │ size: Size(800.0, 600.0)\n' - ' │ axisDirection: down\n' - ' │ crossAxisDirection: right\n' - ' │ offset: ScrollPositionWithSingleContext#00000(offset: 0.0, range:\n' - ' │ 0.0..39400.0, viewport: 600.0, ScrollableState,\n' - ' │ AlwaysScrollableScrollPhysics -> ClampingScrollPhysics,\n' - ' │ IdleScrollActivity#00000, ScrollDirection.idle)\n' - ' │ anchor: 0.0\n' ' │\n' - ' └─center child: RenderSliverFixedExtentList#00000 relayoutBoundary=up1\n' - ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n' - ' │ constraints: SliverConstraints(AxisDirection.down,\n' - ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' - ' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' - ' │ crossAxisDirection: AxisDirection.right,\n' - ' │ viewportMainAxisExtent: 600.0)\n' - ' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n' - ' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n' - ' │ currently live children: 0 to 1\n' + ' └─child: RenderIgnorePointer#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' + ' │ size: Size(800.0, 600.0)\n' + ' │ ignoring: false\n' + ' │ ignoringSemantics: false\n' ' │\n' - ' ├─child with index 0: RenderLimitedBox#00000\n' - ' │ │ parentData: index=0; layoutOffset=0.0\n' - ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ │ size: Size(800.0, 400.0)\n' - ' │ │ maxWidth: 400.0\n' - ' │ │ maxHeight: 400.0\n' - ' │ │\n' - ' │ └─child: RenderCustomPaint#00000\n' - ' │ parentData: (can use size)\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │\n' - ' └─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here - ' │ parentData: index=1; layoutOffset=400.0\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │ maxWidth: 400.0\n' - ' │ maxHeight: 400.0\n' + ' └─child: RenderViewport#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' + ' │ layer: OffsetLayer#00000\n' + ' │ size: Size(800.0, 600.0)\n' + ' │ axisDirection: down\n' + ' │ crossAxisDirection: right\n' + ' │ offset: ScrollPositionWithSingleContext#00000(offset: 0.0, range:\n' + ' │ 0.0..39400.0, viewport: 600.0, ScrollableState,\n' + ' │ AlwaysScrollableScrollPhysics -> ClampingScrollPhysics,\n' + ' │ IdleScrollActivity#00000, ScrollDirection.idle)\n' + ' │ anchor: 0.0\n' ' │\n' - ' └─child: RenderCustomPaint#00000\n' - ' parentData: (can use size)\n' - ' constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' size: Size(800.0, 400.0)\n' + ' └─center child: RenderSliverFixedExtentList#00000 relayoutBoundary=up1\n' + ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n' + ' │ constraints: SliverConstraints(AxisDirection.down,\n' + ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' + ' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' + ' │ crossAxisDirection: AxisDirection.right,\n' + ' │ viewportMainAxisExtent: 600.0)\n' + ' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n' + ' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n' + ' │ currently live children: 0 to 1\n' + ' │\n' + ' ├─child with index 0: RenderLimitedBox#00000\n' + ' │ │ parentData: index=0; layoutOffset=0.0\n' + ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ │ size: Size(800.0, 400.0)\n' + ' │ │ maxWidth: 400.0\n' + ' │ │ maxHeight: 400.0\n' + ' │ │\n' + ' │ └─child: RenderCustomPaint#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │\n' + ' └─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here + ' │ parentData: index=1; layoutOffset=400.0\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │ maxWidth: 400.0\n' + ' │ maxHeight: 400.0\n' + ' │\n' + ' └─child: RenderCustomPaint#00000\n' + ' parentData: (can use size)\n' + ' constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' size: Size(800.0, 400.0)\n' )); const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(true); await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); @@ -324,97 +335,108 @@ void main() { ' │ diagnosis: insufficient data to draw conclusion (less than five\n' ' │ repaints)\n' ' │\n' - ' └─child: RenderSemanticsGestureHandler#00000\n' + ' └─child: _RenderExcludableScrollSemantics#00000\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' + ' │ semantic boundary\n' ' │ size: Size(800.0, 600.0)\n' - ' │ gestures: vertical scroll\n' ' │\n' - ' └─child: RenderPointerListener#00000\n' + ' └─child: RenderSemanticsGestureHandler#00000\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ size: Size(800.0, 600.0)\n' - ' │ behavior: opaque\n' - ' │ listeners: down\n' + ' │ gestures: vertical scroll\n' ' │\n' - ' └─child: RenderIgnorePointer#00000\n' + ' └─child: RenderPointerListener#00000\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ size: Size(800.0, 600.0)\n' - ' │ ignoring: false\n' - ' │ ignoringSemantics: false\n' + ' │ behavior: opaque\n' + ' │ listeners: down\n' ' │\n' - ' └─child: RenderViewport#00000\n' + ' └─child: RenderSemanticsAnnotations#00000\n' ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' - ' │ layer: OffsetLayer#00000\n' ' │ size: Size(800.0, 600.0)\n' - ' │ axisDirection: down\n' - ' │ crossAxisDirection: right\n' - ' │ offset: ScrollPositionWithSingleContext#00000(offset: 2000.0,\n' - ' │ range: 0.0..39400.0, viewport: 600.0, ScrollableState,\n' - ' │ AlwaysScrollableScrollPhysics -> ClampingScrollPhysics,\n' - ' │ IdleScrollActivity#00000, ScrollDirection.idle)\n' - ' │ anchor: 0.0\n' ' │\n' - ' └─center child: RenderSliverFixedExtentList#00000 relayoutBoundary=up1\n' - ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n' - ' │ constraints: SliverConstraints(AxisDirection.down,\n' - ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' - ' │ 2000.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' - ' │ crossAxisDirection: AxisDirection.right,\n' - ' │ viewportMainAxisExtent: 600.0)\n' - ' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n' - ' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n' - ' │ currently live children: 5 to 6\n' + ' └─child: RenderIgnorePointer#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' + ' │ size: Size(800.0, 600.0)\n' + ' │ ignoring: false\n' + ' │ ignoringSemantics: false\n' ' │\n' - ' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0 - ' │ │ parentData: index=5; layoutOffset=2000.0\n' - ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ │ size: Size(800.0, 400.0)\n' - ' │ │ maxWidth: 400.0\n' - ' │ │ maxHeight: 400.0\n' - ' │ │\n' - ' │ └─child: RenderCustomPaint#00000\n' - ' │ parentData: (can use size)\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │\n' - ' ├─child with index 6: RenderLimitedBox#00000\n' - ' ╎ │ parentData: index=6; layoutOffset=2400.0\n' - ' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' ╎ │ size: Size(800.0, 400.0)\n' - ' ╎ │ maxWidth: 400.0\n' - ' ╎ │ maxHeight: 400.0\n' - ' ╎ │\n' - ' ╎ └─child: RenderCustomPaint#00000\n' - ' ╎ parentData: (can use size)\n' - ' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' ╎ size: Size(800.0, 400.0)\n' - ' ╎\n' - ' ╎╌child with index 0 (kept alive offstage): RenderLimitedBox#00000\n' // <----- this one is index 0 and is marked as being offstage - ' ╎ │ parentData: index=0; keepAlive; layoutOffset=0.0\n' - ' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' ╎ │ size: Size(800.0, 400.0)\n' - ' ╎ │ maxWidth: 400.0\n' - ' ╎ │ maxHeight: 400.0\n' - ' ╎ │\n' - ' ╎ └─child: RenderCustomPaint#00000\n' - ' ╎ parentData: (can use size)\n' - ' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' ╎ size: Size(800.0, 400.0)\n' - ' ╎\n' // <----- dashed line ends here - ' └╌child with index 3 (kept alive offstage): RenderLimitedBox#00000\n' - ' │ parentData: index=3; keepAlive; layoutOffset=1200.0\n' - ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' │ size: Size(800.0, 400.0)\n' - ' │ maxWidth: 400.0\n' - ' │ maxHeight: 400.0\n' + ' └─child: RenderViewport#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' + ' │ layer: OffsetLayer#00000\n' + ' │ size: Size(800.0, 600.0)\n' + ' │ axisDirection: down\n' + ' │ crossAxisDirection: right\n' + ' │ offset: ScrollPositionWithSingleContext#00000(offset: 2000.0,\n' + ' │ range: 0.0..39400.0, viewport: 600.0, ScrollableState,\n' + ' │ AlwaysScrollableScrollPhysics -> ClampingScrollPhysics,\n' + ' │ IdleScrollActivity#00000, ScrollDirection.idle)\n' + ' │ anchor: 0.0\n' ' │\n' - ' └─child: RenderCustomPaint#00000\n' - ' parentData: (can use size)\n' - ' constraints: BoxConstraints(w=800.0, h=400.0)\n' - ' size: Size(800.0, 400.0)\n' + ' └─center child: RenderSliverFixedExtentList#00000 relayoutBoundary=up1\n' + ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n' + ' │ constraints: SliverConstraints(AxisDirection.down,\n' + ' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n' + ' │ 2000.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n' + ' │ crossAxisDirection: AxisDirection.right,\n' + ' │ viewportMainAxisExtent: 600.0)\n' + ' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n' + ' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n' + ' │ currently live children: 5 to 6\n' + ' │\n' + ' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0 + ' │ │ parentData: index=5; layoutOffset=2000.0\n' + ' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ │ size: Size(800.0, 400.0)\n' + ' │ │ maxWidth: 400.0\n' + ' │ │ maxHeight: 400.0\n' + ' │ │\n' + ' │ └─child: RenderCustomPaint#00000\n' + ' │ parentData: (can use size)\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │\n' + ' ├─child with index 6: RenderLimitedBox#00000\n' + ' ╎ │ parentData: index=6; layoutOffset=2400.0\n' + ' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' ╎ │ size: Size(800.0, 400.0)\n' + ' ╎ │ maxWidth: 400.0\n' + ' ╎ │ maxHeight: 400.0\n' + ' ╎ │\n' + ' ╎ └─child: RenderCustomPaint#00000\n' + ' ╎ parentData: (can use size)\n' + ' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' ╎ size: Size(800.0, 400.0)\n' + ' ╎\n' + ' ╎╌child with index 0 (kept alive offstage): RenderLimitedBox#00000\n' // <----- this one is index 0 and is marked as being offstage + ' ╎ │ parentData: index=0; keepAlive; layoutOffset=0.0\n' + ' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' ╎ │ size: Size(800.0, 400.0)\n' + ' ╎ │ maxWidth: 400.0\n' + ' ╎ │ maxHeight: 400.0\n' + ' ╎ │\n' + ' ╎ └─child: RenderCustomPaint#00000\n' + ' ╎ parentData: (can use size)\n' + ' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' ╎ size: Size(800.0, 400.0)\n' + ' ╎\n' // <----- dashed line ends here + ' └╌child with index 3 (kept alive offstage): RenderLimitedBox#00000\n' + ' │ parentData: index=3; keepAlive; layoutOffset=1200.0\n' + ' │ constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' │ size: Size(800.0, 400.0)\n' + ' │ maxWidth: 400.0\n' + ' │ maxHeight: 400.0\n' + ' │\n' + ' └─child: RenderCustomPaint#00000\n' + ' parentData: (can use size)\n' + ' constraints: BoxConstraints(w=800.0, h=400.0)\n' + ' size: Size(800.0, 400.0)\n' )); }); diff --git a/packages/flutter/test/widgets/modal_barrier_test.dart b/packages/flutter/test/widgets/modal_barrier_test.dart index 59afd8f583..ab5f432fe4 100644 --- a/packages/flutter/test/widgets/modal_barrier_test.dart +++ b/packages/flutter/test/widgets/modal_barrier_test.dart @@ -110,7 +110,6 @@ void main() { final TestSemantics expectedSemantics = new TestSemantics.root( children: [ new TestSemantics.rootChild( - id: 2, rect: TestSemantics.fullScreen, actions: SemanticsAction.tap.index, label: 'Dismiss', @@ -118,7 +117,7 @@ void main() { ), ] ); - expect(semantics, hasSemantics(expectedSemantics)); + expect(semantics, hasSemantics(expectedSemantics, ignoreId: true)); semantics.dispose(); debugDefaultTargetPlatformOverride = null; diff --git a/packages/flutter/test/widgets/semantics_merge_test.dart b/packages/flutter/test/widgets/semantics_merge_test.dart index 26cd124f12..5e1ecc4d7c 100644 --- a/packages/flutter/test/widgets/semantics_merge_test.dart +++ b/packages/flutter/test/widgets/semantics_merge_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:ui' show SemanticsFlag; + import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -10,6 +12,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'semantics_tester.dart'; void main() { + setUp(() { + debugResetSemanticsIdCounter(); + }); + testWidgets('MergeSemantics', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); @@ -19,8 +25,14 @@ void main() { textDirection: TextDirection.ltr, child: new Row( children: [ - const Text('test1'), - const Text('test2'), + new Semantics( + container: true, + child: const Text('test1'), + ), + new Semantics( + container: true, + child: const Text('test2'), + ), ], ), ), @@ -44,8 +56,14 @@ void main() { child: new MergeSemantics( child: new Row( children: [ - const Text('test1'), - const Text('test2'), + new Semantics( + container: true, + child: const Text('test1'), + ), + new Semantics( + container: true, + child: const Text('test2'), + ), ], ), ), @@ -71,8 +89,14 @@ void main() { textDirection: TextDirection.ltr, child: new Row( children: [ - const Text('test1'), - const Text('test2'), + new Semantics( + container: true, + child: const Text('test1'), + ), + new Semantics( + container: true, + child: const Text('test2'), + ), ], ), ), @@ -81,8 +105,8 @@ void main() { expect(semantics, hasSemantics( new TestSemantics.root( children: [ - new TestSemantics.rootChild(id: 4, label: 'test1'), - new TestSemantics.rootChild(id: 5, label: 'test2'), + new TestSemantics.rootChild(id: 6, label: 'test1'), + new TestSemantics.rootChild(id: 7, label: 'test2'), ], ), ignoreRect: true, @@ -91,4 +115,49 @@ void main() { semantics.dispose(); }); + + testWidgets('MergeSemantics works if other nodes are implicitly merged into its node', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.ltr, + child: new MergeSemantics( + child: new Semantics( + selected: true, // this is implicitly merged into the MergeSemantics node + child: new Row( + children: [ + new Semantics( + container: true, + child: const Text('test1'), + ), + new Semantics( + container: true, + child: const Text('test2'), + ), + ], + ), + ), + ), + ), + ); + + expect(semantics, hasSemantics( + new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + flags: [ + SemanticsFlag.isSelected, + ], + label: 'test1\ntest2', + ), + ] + ), + ignoreRect: true, + ignoreTransform: true, + )); + + semantics.dispose(); + }); } diff --git a/packages/flutter/test/widgets/sliver_semantics_test.dart b/packages/flutter/test/widgets/sliver_semantics_test.dart index dc359628e3..724ea4a33a 100644 --- a/packages/flutter/test/widgets/sliver_semantics_test.dart +++ b/packages/flutter/test/widgets/sliver_semantics_test.dart @@ -51,7 +51,7 @@ void main() { children: [ new TestSemantics.rootChild( id: 1, - tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], + tags: [RenderViewport.useTwoPaneSemantics], children: [ new TestSemantics( id: 5, @@ -89,7 +89,7 @@ void main() { children: [ new TestSemantics.rootChild( id: 1, - tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], + tags: [RenderViewport.useTwoPaneSemantics], children: [ new TestSemantics( id: 5, @@ -112,7 +112,7 @@ void main() { new TestSemantics( id: 4, label: 'Semantics Test with Slivers', - tags: [RenderSemanticsGestureHandler.excludeFromScrolling], + tags: [RenderViewport.excludeFromScrolling], ), ], ) @@ -132,7 +132,7 @@ void main() { children: [ new TestSemantics.rootChild( id: 1, - tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], + tags: [RenderViewport.useTwoPaneSemantics], children: [ new TestSemantics( id: 5, @@ -203,7 +203,7 @@ void main() { children: [ new TestSemantics.rootChild( id: 7, - tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], + tags: [RenderViewport.useTwoPaneSemantics], children: [ new TestSemantics( id: 10, @@ -257,7 +257,7 @@ void main() { children: [ new TestSemantics.rootChild( id: 11, - tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], + tags: [RenderViewport.useTwoPaneSemantics], children: [ new TestSemantics( id: 17, @@ -339,7 +339,7 @@ void main() { new TestSemantics.rootChild( id: 18, rect: TestSemantics.fullScreen, - tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], + tags: [RenderViewport.useTwoPaneSemantics], children: [ new TestSemantics( id: 23, @@ -371,7 +371,7 @@ void main() { new TestSemantics( id: 22, rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0), - tags: [RenderSemanticsGestureHandler.excludeFromScrolling], + tags: [RenderViewport.excludeFromScrolling], label: 'AppBar', ), ], @@ -422,7 +422,7 @@ void main() { new TestSemantics.rootChild( id: 24, rect: TestSemantics.fullScreen, - tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], + tags: [RenderViewport.useTwoPaneSemantics], children: [ new TestSemantics( id: 29, @@ -454,7 +454,7 @@ void main() { new TestSemantics( id: 28, rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0), - tags: [RenderSemanticsGestureHandler.excludeFromScrolling], + tags: [RenderViewport.excludeFromScrolling], label: 'AppBar' ), ], @@ -507,7 +507,7 @@ void main() { new TestSemantics.rootChild( id: 30, rect: TestSemantics.fullScreen, - tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], + tags: [RenderViewport.useTwoPaneSemantics], children: [ new TestSemantics( id: 35, @@ -540,7 +540,7 @@ void main() { id: 34, rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0), transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)), - tags: [RenderSemanticsGestureHandler.excludeFromScrolling], + tags: [RenderViewport.excludeFromScrolling], label: 'AppBar' ), ], @@ -592,7 +592,7 @@ void main() { new TestSemantics.rootChild( id: 36, rect: TestSemantics.fullScreen, - tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], + tags: [RenderViewport.useTwoPaneSemantics], children: [ new TestSemantics( id: 41, @@ -625,7 +625,7 @@ void main() { id: 40, rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0), transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)), - tags: [RenderSemanticsGestureHandler.excludeFromScrolling], + tags: [RenderViewport.excludeFromScrolling], label: 'AppBar' ), ],