Commit f8a2bd20 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Redesign Semantic Tree Compilation Algorithm (#12605)

* Oct 12 10:12am

* implicit_semantics_test.dart passes

* refactoring

* works in nice

* minor rename

* more doc comments

* to be explicit check better

* fix test

* ++

* ++

* semantics_9_test (BlockSemantics) and implicit_semantics_test are passing

* doc updates

* tiny refactor

* fix static errors in tests

* fix gesture detector

* ++

* ++

* geometry

* ++

* remove noGeometry

* revert test

* +

* all tests but scrolling/clipping pass

* clipping works

* scrolling halfway

* sliver tests pass

* ALL TESTS PASS

* SemanticsNode changed

* docs and tiny fixes

* card test

* more doc comments

* remove missed print

* more tests

* make test pass on Linux

* remove changes to intellij proj file

* review comments
parent d47d2687
...@@ -251,8 +251,7 @@ class RecipeCard extends StatelessWidget { ...@@ -251,8 +251,7 @@ class RecipeCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new MergeSemantics( return new GestureDetector(
child: new GestureDetector(
onTap: onTap, onTap: onTap,
child: new Card( child: new Card(
child: new Column( child: new Column(
...@@ -294,7 +293,6 @@ class RecipeCard extends StatelessWidget { ...@@ -294,7 +293,6 @@ class RecipeCard extends StatelessWidget {
], ],
), ),
), ),
),
); );
} }
} }
......
...@@ -31,8 +31,7 @@ class GalleryItem extends StatelessWidget { ...@@ -31,8 +31,7 @@ class GalleryItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new MergeSemantics( return new ListTile(
child: new ListTile(
title: new Text(title), title: new Text(title),
subtitle: new Text(subtitle), subtitle: new Text(subtitle),
onTap: () { onTap: () {
...@@ -44,7 +43,6 @@ class GalleryItem extends StatelessWidget { ...@@ -44,7 +43,6 @@ class GalleryItem extends StatelessWidget {
Navigator.pushNamed(context, routeName); Navigator.pushNamed(context, routeName);
} }
} }
),
); );
} }
} }
......
...@@ -187,7 +187,7 @@ final Duration _kDiscreteTransitionDuration = const Duration(milliseconds: 500); ...@@ -187,7 +187,7 @@ final Duration _kDiscreteTransitionDuration = const Duration(milliseconds: 500);
const double _kAdjustmentUnit = 0.1; // Matches iOS implementation of material slider. const double _kAdjustmentUnit = 0.1; // Matches iOS implementation of material slider.
class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsActionHandler { class _RenderCupertinoSlider extends RenderConstrainedBox {
_RenderCupertinoSlider({ _RenderCupertinoSlider({
@required double value, @required double value,
int divisions, int divisions,
...@@ -253,7 +253,7 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc ...@@ -253,7 +253,7 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc
final bool wasInteractive = isInteractive; final bool wasInteractive = isInteractive;
_onChanged = value; _onChanged = value;
if (wasInteractive != isInteractive) if (wasInteractive != isInteractive)
markNeedsSemanticsUpdate(noGeometry: true); markNeedsSemanticsUpdate();
} }
TextDirection get textDirection => _textDirection; TextDirection get textDirection => _textDirection;
...@@ -379,31 +379,25 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc ...@@ -379,31 +379,25 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc
} }
@override @override
bool get isSemanticBoundary => isInteractive; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
@override config.isSemanticBoundary = isInteractive;
SemanticsAnnotator get semanticsAnnotator => _annotate; if (isInteractive) {
config.addAction(SemanticsAction.increase, _increaseAction);
config.addAction(SemanticsAction.decrease, _decreaseAction);
}
}
double get _semanticActionUnit => divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
void _annotate(SemanticsNode semantics) { void _increaseAction() {
if (isInteractive) if (isInteractive)
semantics.addAdjustmentActions(); onChanged((value + _semanticActionUnit).clamp(0.0, 1.0));
} }
@override void _decreaseAction() {
void performAction(SemanticsAction action) {
final double unit = divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
switch (action) {
case SemanticsAction.increase:
if (isInteractive) if (isInteractive)
onChanged((value + unit).clamp(0.0, 1.0)); onChanged((value - _semanticActionUnit).clamp(0.0, 1.0));
break;
case SemanticsAction.decrease:
if (isInteractive)
onChanged((value - unit).clamp(0.0, 1.0));
break;
default:
assert(false);
break;
}
} }
} }
...@@ -156,7 +156,7 @@ const Color _kTrackColor = CupertinoColors.lightBackgroundGray; ...@@ -156,7 +156,7 @@ const Color _kTrackColor = CupertinoColors.lightBackgroundGray;
const Duration _kReactionDuration = const Duration(milliseconds: 300); const Duration _kReactionDuration = const Duration(milliseconds: 300);
const Duration _kToggleDuration = const Duration(milliseconds: 200); const Duration _kToggleDuration = const Duration(milliseconds: 200);
class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsActionHandler { class _RenderCupertinoSwitch extends RenderConstrainedBox {
_RenderCupertinoSwitch({ _RenderCupertinoSwitch({
@required bool value, @required bool value,
@required Color activeColor, @required Color activeColor,
...@@ -214,7 +214,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc ...@@ -214,7 +214,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
if (value == _value) if (value == _value)
return; return;
_value = value; _value = value;
markNeedsSemanticsUpdate(onlyLocalUpdates: true, noGeometry: true); markNeedsSemanticsUpdate(onlyLocalUpdates: true);
_position _position
..curve = Curves.ease ..curve = Curves.ease
..reverseCurve = Curves.ease.flipped; ..reverseCurve = Curves.ease.flipped;
...@@ -254,7 +254,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc ...@@ -254,7 +254,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
_onChanged = value; _onChanged = value;
if (wasInteractive != isInteractive) { if (wasInteractive != isInteractive) {
markNeedsPaint(); markNeedsPaint();
markNeedsSemanticsUpdate(noGeometry: true); markNeedsSemanticsUpdate();
} }
} }
...@@ -375,23 +375,13 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc ...@@ -375,23 +375,13 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
} }
@override @override
bool get isSemanticBoundary => isInteractive; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
@override config.isSemanticBoundary = isInteractive;
SemanticsAnnotator get semanticsAnnotator => _annotate;
void _annotate(SemanticsNode semantics) {
semantics
..hasCheckedState = true
..isChecked = _value;
if (isInteractive) if (isInteractive)
semantics.addAction(SemanticsAction.tap); config.addAction(SemanticsAction.tap, _handleTap);
} config.isChecked = _value;
@override
void performAction(SemanticsAction action) {
if (action == SemanticsAction.tap)
_handleTap();
} }
final CupertinoThumbPainter _thumbPainter = new CupertinoThumbPainter(); final CupertinoThumbPainter _thumbPainter = new CupertinoThumbPainter();
......
...@@ -80,7 +80,9 @@ class Card extends StatelessWidget { ...@@ -80,7 +80,9 @@ class Card extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Container( return new Semantics(
container: true,
child: new Container(
margin: const EdgeInsets.all(4.0), margin: const EdgeInsets.all(4.0),
child: new Material( child: new Material(
color: color, color: color,
...@@ -88,6 +90,7 @@ class Card extends StatelessWidget { ...@@ -88,6 +90,7 @@ class Card extends StatelessWidget {
elevation: elevation, elevation: elevation,
child: child child: child
) )
),
); );
} }
} }
...@@ -307,7 +307,7 @@ double _getPreferredTotalHeight(String label) { ...@@ -307,7 +307,7 @@ double _getPreferredTotalHeight(String label) {
return 2 * _kReactionRadius + _getAdditionalHeightForLabel(label); return 2 * _kReactionRadius + _getAdditionalHeightForLabel(label);
} }
class _RenderSlider extends RenderBox implements SemanticsActionHandler { class _RenderSlider extends RenderBox {
_RenderSlider({ _RenderSlider({
@required double value, @required double value,
int divisions, int divisions,
...@@ -441,7 +441,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ...@@ -441,7 +441,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
_onChanged = value; _onChanged = value;
if (wasInteractive != isInteractive) { if (wasInteractive != isInteractive) {
markNeedsPaint(); markNeedsPaint();
markNeedsSemanticsUpdate(noGeometry: true); markNeedsSemanticsUpdate();
} }
} }
...@@ -708,31 +708,25 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ...@@ -708,31 +708,25 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
} }
@override @override
bool get isSemanticBoundary => isInteractive; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
@override config.isSemanticBoundary = isInteractive;
SemanticsAnnotator get semanticsAnnotator => _annotate; if (isInteractive) {
config.addAction(SemanticsAction.increase, _increaseAction);
config.addAction(SemanticsAction.decrease, _decreaseAction);
}
}
double get _semanticActionUnit => divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
void _annotate(SemanticsNode semantics) { void _increaseAction() {
if (isInteractive) if (isInteractive)
semantics.addAdjustmentActions(); onChanged((value + _semanticActionUnit).clamp(0.0, 1.0));
} }
@override void _decreaseAction() {
void performAction(SemanticsAction action) {
final double unit = divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
switch (action) {
case SemanticsAction.increase:
if (isInteractive) if (isInteractive)
onChanged((value + unit).clamp(0.0, 1.0)); onChanged((value - _semanticActionUnit).clamp(0.0, 1.0));
break;
case SemanticsAction.decrease:
if (isInteractive)
onChanged((value - unit).clamp(0.0, 1.0));
break;
default:
assert(false);
break;
}
} }
} }
...@@ -754,22 +754,20 @@ class _TabBarState extends State<TabBar> { ...@@ -754,22 +754,20 @@ class _TabBarState extends State<TabBar> {
// reflect the intrinsic width of their labels. // reflect the intrinsic width of their labels.
final int tabCount = widget.tabs.length; final int tabCount = widget.tabs.length;
for (int index = 0; index < tabCount; index++) { for (int index = 0; index < tabCount; index++) {
wrappedTabs[index] = new MergeSemantics( wrappedTabs[index] = new InkWell(
child: new Stack(
children: <Widget>[
new InkWell(
onTap: () { _handleTap(index); }, onTap: () { _handleTap(index); },
child: new Padding( child: new Padding(
padding: new EdgeInsets.only(bottom: widget.indicatorWeight), padding: new EdgeInsets.only(bottom: widget.indicatorWeight),
child: wrappedTabs[index], child: new Stack(
), children: <Widget>[
), wrappedTabs[index],
new Semantics( new Semantics(
selected: index == _currentIndex, selected: index == _currentIndex,
// TODO(goderbauer): I10N-ify // TODO(goderbauer): I10N-ify
label: 'Tab ${index + 1} of $tabCount', label: 'Tab ${index + 1} of $tabCount',
), ),
], ]
),
), ),
); );
if (!widget.isScrollable) if (!widget.isScrollable)
......
...@@ -18,7 +18,7 @@ final Tween<double> _kRadialReactionRadiusTween = new Tween<double>(begin: 0.0, ...@@ -18,7 +18,7 @@ final Tween<double> _kRadialReactionRadiusTween = new Tween<double>(begin: 0.0,
/// This class handles storing the current value, dispatching ValueChanged on a /// This class handles storing the current value, dispatching ValueChanged on a
/// tap gesture and driving a changed animation. Subclasses are responsible for /// tap gesture and driving a changed animation. Subclasses are responsible for
/// painting. /// painting.
abstract class RenderToggleable extends RenderConstrainedBox implements SemanticsActionHandler { abstract class RenderToggleable extends RenderConstrainedBox {
/// Creates a toggleable render object. /// Creates a toggleable render object.
/// ///
/// The [value], [activeColor], and [inactiveColor] arguments must not be /// The [value], [activeColor], and [inactiveColor] arguments must not be
...@@ -122,7 +122,7 @@ abstract class RenderToggleable extends RenderConstrainedBox implements Semantic ...@@ -122,7 +122,7 @@ abstract class RenderToggleable extends RenderConstrainedBox implements Semantic
if (value == _value) if (value == _value)
return; return;
_value = value; _value = value;
markNeedsSemanticsUpdate(onlyLocalUpdates: true, noGeometry: true); markNeedsSemanticsUpdate(onlyLocalUpdates: true);
_position _position
..curve = Curves.easeIn ..curve = Curves.easeIn
..reverseCurve = Curves.easeOut; ..reverseCurve = Curves.easeOut;
...@@ -178,7 +178,7 @@ abstract class RenderToggleable extends RenderConstrainedBox implements Semantic ...@@ -178,7 +178,7 @@ abstract class RenderToggleable extends RenderConstrainedBox implements Semantic
_onChanged = value; _onChanged = value;
if (wasInteractive != isInteractive) { if (wasInteractive != isInteractive) {
markNeedsPaint(); markNeedsPaint();
markNeedsSemanticsUpdate(noGeometry: true); markNeedsSemanticsUpdate();
} }
} }
...@@ -283,23 +283,13 @@ abstract class RenderToggleable extends RenderConstrainedBox implements Semantic ...@@ -283,23 +283,13 @@ abstract class RenderToggleable extends RenderConstrainedBox implements Semantic
} }
@override @override
bool get isSemanticBoundary => isInteractive; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
@override config.isSemanticBoundary = isInteractive;
SemanticsAnnotator get semanticsAnnotator => _annotate;
void _annotate(SemanticsNode semantics) {
semantics
..hasCheckedState = true
..isChecked = _value;
if (isInteractive) if (isInteractive)
semantics.addAction(SemanticsAction.tap); config.addAction(SemanticsAction.tap, _handleTap);
} config.isChecked = _value;
@override
void performAction(SemanticsAction action) {
if (action == SemanticsAction.tap)
_handleTap();
} }
@override @override
......
...@@ -404,11 +404,11 @@ class RenderParagraph extends RenderBox { ...@@ -404,11 +404,11 @@ class RenderParagraph extends RenderBox {
} }
@override @override
SemanticsAnnotator get semanticsAnnotator => _annotate; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
void _annotate(SemanticsNode node) { config
node.label = text.toPlainText(); ..label = text.toPlainText()
node.textDirection = textDirection; ..textDirection = textDirection;
} }
@override @override
......
...@@ -221,10 +221,11 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje ...@@ -221,10 +221,11 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje
} }
@override @override
SemanticsAnnotator get semanticsAnnotator => _excludeFromSemanticsScrolling ? _annotate : null; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
void _annotate(SemanticsNode node) { if (_excludeFromSemanticsScrolling)
node.addTag(RenderSemanticsGestureHandler.excludeFromScrolling); config.addTagForChildren(RenderSemanticsGestureHandler.excludeFromScrolling);
} }
@override @override
......
...@@ -91,11 +91,12 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -91,11 +91,12 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
_crossAxisDirection = crossAxisDirection, _crossAxisDirection = crossAxisDirection,
_offset = offset; _offset = offset;
@override @override
SemanticsAnnotator get semanticsAnnotator => _annotate; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
void _annotate(SemanticsNode node) { config.addTagForChildren(RenderSemanticsGestureHandler.useTwoPaneSemantics);
node.addTag(RenderSemanticsGestureHandler.useTwoPaneSemantics);
} }
@override @override
......
...@@ -4434,6 +4434,7 @@ class Semantics extends SingleChildRenderObjectWidget { ...@@ -4434,6 +4434,7 @@ class Semantics extends SingleChildRenderObjectWidget {
Key key, Key key,
Widget child, Widget child,
this.container: false, this.container: false,
this.explicitChildNodes: false,
this.checked, this.checked,
this.selected, this.selected,
this.label, this.label,
...@@ -4441,18 +4442,29 @@ class Semantics extends SingleChildRenderObjectWidget { ...@@ -4441,18 +4442,29 @@ class Semantics extends SingleChildRenderObjectWidget {
}) : assert(container != null), }) : assert(container != null),
super(key: key, child: child); super(key: key, child: child);
/// If 'container' is true, this Widget will introduce a new node in /// If 'container' is true, this widget will introduce a new
/// the semantics tree. Otherwise, the semantics will be merged with /// node in the semantics tree. Otherwise, the semantics will be
/// the semantics of any ancestors. /// merged with the semantics of any ancestors (if the ancestor allows that).
/// ///
/// The 'container' flag is implicitly set to true on the immediate /// Whether descendants of this widget can add their semantic information to the
/// semantics-providing descendants of a node where multiple /// [SemanticsNode] introduced by this configuration is controlled by
/// children have semantics or have descendants providing semantics. /// [explicitChildNodes].
/// In other words, the semantics of siblings are not merged. To
/// merge the semantics of an entire subtree, including siblings,
/// you can use a [MergeSemantics] widget.
final bool container; final bool container;
/// Whether descendants of this widget are allowed to add semantic information
/// to the [SemanticsNode] annotated by this widget.
///
/// When set to false descendants are allowed to annotate [SemanticNode]s of
/// their parent with the semantic information they want to contribute to the
/// semantic tree.
/// When set to true the only way for descendants to contribute semantic
/// information to the semantic tree is to introduce new explicit
/// [SemanticNode]s to the tree.
///
/// This setting is often used in combination with [isSemanticBoundary] to
/// create semantic boundaries that are either writable or not for children.
final bool explicitChildNodes;
/// If non-null, indicates that this subtree represents a checkbox /// If non-null, indicates that this subtree represents a checkbox
/// or similar widget with a "checked" state, and what its current /// or similar widget with a "checked" state, and what its current
/// state is. /// state is.
...@@ -4484,6 +4496,7 @@ class Semantics extends SingleChildRenderObjectWidget { ...@@ -4484,6 +4496,7 @@ class Semantics extends SingleChildRenderObjectWidget {
RenderSemanticsAnnotations createRenderObject(BuildContext context) { RenderSemanticsAnnotations createRenderObject(BuildContext context) {
return new RenderSemanticsAnnotations( return new RenderSemanticsAnnotations(
container: container, container: container,
explicitChildNodes: explicitChildNodes,
checked: checked, checked: checked,
selected: selected, selected: selected,
label: label, label: label,
...@@ -4495,6 +4508,7 @@ class Semantics extends SingleChildRenderObjectWidget { ...@@ -4495,6 +4508,7 @@ class Semantics extends SingleChildRenderObjectWidget {
void updateRenderObject(BuildContext context, RenderSemanticsAnnotations renderObject) { void updateRenderObject(BuildContext context, RenderSemanticsAnnotations renderObject) {
renderObject renderObject
..container = container ..container = container
..explicitChildNodes = explicitChildNodes
..checked = checked ..checked = checked
..selected = selected ..selected = selected
..label = label ..label = label
......
...@@ -104,7 +104,7 @@ class _FocusScopeState extends State<FocusScope> { ...@@ -104,7 +104,7 @@ class _FocusScopeState extends State<FocusScope> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
FocusScope.of(context).reparentScopeIfNeeded(widget.node); FocusScope.of(context).reparentScopeIfNeeded(widget.node);
return new Semantics( return new Semantics(
container: true, explicitChildNodes: true,
child: new _FocusScopeMarker( child: new _FocusScopeMarker(
node: widget.node, node: widget.node,
child: widget.child, child: widget.child,
......
// 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';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart';
void main() {
testWidgets('Card can take semantic text from multiple children', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new Center(
child: new Card(
child: new Column(
children: <Widget>[
const Text('I am text!'),
const Text('Moar text!!1'),
new MaterialButton(
child: const Text('Button'),
onPressed: () { },
)
],
)
),
),
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
label: 'I am text!\nMoar text!!1',
textDirection: TextDirection.ltr,
children: <TestSemantics>[
new TestSemantics(
id: 1,
label: 'Button',
textDirection: TextDirection.ltr,
actions: SemanticsAction.tap.index,
),
],
),
],
),
ignoreTransform: true,
ignoreRect: true,
));
semantics.dispose();
});
}
...@@ -95,7 +95,7 @@ void main() { ...@@ -95,7 +95,7 @@ void main() {
expect(semantics, hasSemantics(new TestSemantics.root( expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 1, id: 7,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0), rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: null, transform: null,
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
...@@ -103,7 +103,7 @@ void main() { ...@@ -103,7 +103,7 @@ void main() {
label: 'aaa\nAAA', label: 'aaa\nAAA',
), ),
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 6, id: 8,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0), rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: new Matrix4.translationValues(0.0, 56.0, 0.0), transform: new Matrix4.translationValues(0.0, 56.0, 0.0),
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
...@@ -111,7 +111,7 @@ void main() { ...@@ -111,7 +111,7 @@ void main() {
label: 'bbb\nBBB', label: 'bbb\nBBB',
), ),
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 11, id: 9,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0), rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: new Matrix4.translationValues(0.0, 112.0, 0.0), transform: new Matrix4.translationValues(0.0, 112.0, 0.0),
flags: SemanticsFlags.hasCheckedState.index, flags: SemanticsFlags.hasCheckedState.index,
......
...@@ -1011,11 +1011,11 @@ void main() { ...@@ -1011,11 +1011,11 @@ void main() {
final TestSemantics expectedSemantics = new TestSemantics.root( final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 1, id: 3,
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 2, id: 1,
actions: SemanticsAction.tap.index, actions: SemanticsAction.tap.index,
flags: SemanticsFlags.isSelected.index, flags: SemanticsFlags.isSelected.index,
label: 'TAB #0\nTab 1 of 2', label: 'TAB #0\nTab 1 of 2',
...@@ -1023,13 +1023,14 @@ void main() { ...@@ -1023,13 +1023,14 @@ void main() {
transform: new Matrix4.translationValues(0.0, 276.0, 0.0), transform: new Matrix4.translationValues(0.0, 276.0, 0.0),
), ),
new TestSemantics( new TestSemantics(
id: 5, id: 2,
actions: SemanticsAction.tap.index, actions: SemanticsAction.tap.index,
label: 'TAB #1\nTab 2 of 2', label: 'TAB #1\nTab 2 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight), rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
transform: new Matrix4.translationValues(108.0, 276.0, 0.0), transform: new Matrix4.translationValues(108.0, 276.0, 0.0),
), ),
]), ],
),
], ],
); );
...@@ -1064,15 +1065,19 @@ void main() { ...@@ -1064,15 +1065,19 @@ void main() {
), ),
); );
const String tab0title = 'This is a very wide tab #0\nTab 1 of 20';
const String tab10title = 'This is a very wide tab #10\nTab 11 of 20';
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft])); expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft]));
expect(semantics, isNot(includesNodeWith(label: 'This is a very wide tab #10'))); expect(semantics, includesNodeWith(label: tab0title));
expect(semantics, isNot(includesNodeWith(label: tab10title)));
controller.index = 10; controller.index = 10;
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(semantics, isNot(includesNodeWith(label: 'This is a very wide tab #0'))); expect(semantics, isNot(includesNodeWith(label: tab0title)));
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight])); expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight]));
expect(semantics, includesNodeWith(label: 'This is a very wide tab #10')); expect(semantics, includesNodeWith(label: tab10title));
controller.index = 19; controller.index = 19;
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -1083,7 +1088,8 @@ void main() { ...@@ -1083,7 +1088,8 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft])); expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft]));
expect(semantics, includesNodeWith(label: 'This is a very wide tab #0')); expect(semantics, includesNodeWith(label: tab0title));
expect(semantics, isNot(includesNodeWith(label: tab10title)));
semantics.dispose(); semantics.dispose();
}); });
......
...@@ -473,7 +473,7 @@ void main() { ...@@ -473,7 +473,7 @@ void main() {
child: new Tooltip( child: new Tooltip(
key: key, key: key,
message: tooltipText, message: tooltipText,
child: new Container(width: 0.0, height: 0.0), child: new Container(width: 10.0, height: 10.0),
), ),
), ),
], ],
...@@ -485,14 +485,23 @@ void main() { ...@@ -485,14 +485,23 @@ void main() {
), ),
); );
expect(semantics, hasSemantics(new TestSemantics.root(label: tooltipText))); final TestSemantics expected = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
label: tooltipText,
),
]
);
expect(semantics, hasSemantics(expected, ignoreTransform: true, ignoreRect: true));
// before using "as dynamic" in your code, see note top of file // before using "as dynamic" in your code, see note top of file
(key.currentState as dynamic).ensureTooltipVisible(); // this triggers a rebuild of the semantics because the tree changes (key.currentState as dynamic).ensureTooltipVisible(); // this triggers a rebuild of the semantics because the tree changes
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
expect(semantics, hasSemantics(new TestSemantics.root(label: tooltipText))); expect(semantics, hasSemantics(expected, ignoreTransform: true, ignoreRect: true));
semantics.dispose(); semantics.dispose();
}); });
......
...@@ -55,6 +55,9 @@ class TestRenderObject extends RenderObject { ...@@ -55,6 +55,9 @@ class TestRenderObject extends RenderObject {
Rect get semanticBounds => new Rect.fromLTWH(0.0, 0.0, 10.0, 20.0); Rect get semanticBounds => new Rect.fromLTWH(0.0, 0.0, 10.0, 20.0);
@override @override
bool get isSemanticBoundary => true; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.isSemanticBoundary = true;
}
} }
...@@ -72,23 +72,23 @@ void main() { ...@@ -72,23 +72,23 @@ void main() {
}); });
test('RenderSemanticsGestureHandler adds/removes correct semantic actions', () { test('RenderSemanticsGestureHandler adds/removes correct semantic actions', () {
SemanticsNode node = new SemanticsNode();
final RenderSemanticsGestureHandler renderObj = new RenderSemanticsGestureHandler( final RenderSemanticsGestureHandler renderObj = new RenderSemanticsGestureHandler(
onTap: () {}, onTap: () {},
onHorizontalDragUpdate: (DragUpdateDetails details) {}, onHorizontalDragUpdate: (DragUpdateDetails details) {},
); );
renderObj.semanticsAnnotator(node); SemanticsConfiguration config = new SemanticsConfiguration();
expect(node.getSemanticsData().hasAction(SemanticsAction.tap), isTrue); renderObj.describeSemanticsConfiguration(config);
expect(node.getSemanticsData().hasAction(SemanticsAction.scrollLeft), isTrue); expect(config.getActionHandler(SemanticsAction.tap), isNotNull);
expect(node.getSemanticsData().hasAction(SemanticsAction.scrollRight), isTrue); expect(config.getActionHandler(SemanticsAction.scrollLeft), isNotNull);
expect(config.getActionHandler(SemanticsAction.scrollRight), isNotNull);
node = new SemanticsNode(); config = new SemanticsConfiguration();
renderObj.validActions = <SemanticsAction>[SemanticsAction.tap, SemanticsAction.scrollLeft].toSet(); renderObj.validActions = <SemanticsAction>[SemanticsAction.tap, SemanticsAction.scrollLeft].toSet();
renderObj.semanticsAnnotator(node); renderObj.describeSemanticsConfiguration(config);
expect(node.getSemanticsData().hasAction(SemanticsAction.tap), isTrue); expect(config.getActionHandler(SemanticsAction.tap), isNotNull);
expect(node.getSemanticsData().hasAction(SemanticsAction.scrollLeft), isTrue); expect(config.getActionHandler(SemanticsAction.scrollLeft), isNotNull);
expect(node.getSemanticsData().hasAction(SemanticsAction.scrollRight), isFalse); expect(config.getActionHandler(SemanticsAction.scrollRight), isNull);
}); });
} }
...@@ -29,6 +29,7 @@ class TestTree { ...@@ -29,6 +29,7 @@ class TestTree {
child: new RenderPositionedBox( child: new RenderPositionedBox(
child: child = new RenderConstrainedBox( child: child = new RenderConstrainedBox(
additionalConstraints: const BoxConstraints.tightFor(height: 20.0, width: 20.0), additionalConstraints: const BoxConstraints.tightFor(height: 20.0, width: 20.0),
child: new RenderSemanticsAnnotations(label: 'Hello there foo', textDirection: TextDirection.ltr)
), ),
), ),
), ),
...@@ -127,7 +128,7 @@ void main() { ...@@ -127,7 +128,7 @@ void main() {
layout(testTree.root, phase: EnginePhase.paint); layout(testTree.root, phase: EnginePhase.paint);
expect(testTree.painted, isTrue); expect(testTree.painted, isTrue);
}); });
test('objects can be detached and re-attached: semantics', () { test('objects can be detached and re-attached: semantics (no change)', () {
final TestTree testTree = new TestTree(); final TestTree testTree = new TestTree();
int semanticsUpdateCount = 0; int semanticsUpdateCount = 0;
final SemanticsHandle semanticsHandle = renderer.pipelineOwner.ensureSemantics( final SemanticsHandle semanticsHandle = renderer.pipelineOwner.ensureSemantics(
...@@ -147,7 +148,31 @@ void main() { ...@@ -147,7 +148,31 @@ void main() {
expect(semanticsUpdateCount, 0); expect(semanticsUpdateCount, 0);
// Lay out, composite, paint, and update semantics again // Lay out, composite, paint, and update semantics again
layout(testTree.root, phase: EnginePhase.flushSemantics); layout(testTree.root, phase: EnginePhase.flushSemantics);
expect(semanticsUpdateCount, 0); // no semantics have changed.
semanticsHandle.dispose();
});
test('objects can be detached and re-attached: semantics (with change)', () {
final TestTree testTree = new TestTree();
int semanticsUpdateCount = 0;
final SemanticsHandle semanticsHandle = renderer.pipelineOwner.ensureSemantics(
listener: () {
++semanticsUpdateCount;
}
);
// Lay out, composite, paint, and update semantics
layout(testTree.root, phase: EnginePhase.flushSemantics);
expect(semanticsUpdateCount, 1); expect(semanticsUpdateCount, 1);
// Remove testTree from the custom render view
renderer.renderView.child = null;
expect(testTree.child.owner, isNull);
// Dirty one of the elements
semanticsUpdateCount = 0;
testTree.child.additionalConstraints = const BoxConstraints.tightFor(height: 20.0, width: 30.0);
testTree.child.markNeedsSemanticsUpdate();
expect(semanticsUpdateCount, 0);
// Lay out, composite, paint, and update semantics again
layout(testTree.root, phase: EnginePhase.flushSemantics);
expect(semanticsUpdateCount, 1); // semantics have changed.
semanticsHandle.dispose(); semanticsHandle.dispose();
}); });
} }
...@@ -18,41 +18,45 @@ void main() { ...@@ -18,41 +18,45 @@ void main() {
test('tagging', () { test('tagging', () {
final SemanticsNode node = new SemanticsNode(); final SemanticsNode node = new SemanticsNode();
expect(node.hasTag(tag1), isFalse); expect(node.isTagged(tag1), isFalse);
expect(node.hasTag(tag2), isFalse); expect(node.isTagged(tag2), isFalse);
node.addTag(tag1); node.tags = new Set<SemanticsTag>()..add(tag1);
expect(node.hasTag(tag1), isTrue); expect(node.isTagged(tag1), isTrue);
expect(node.hasTag(tag2), isFalse); expect(node.isTagged(tag2), isFalse);
node.addTag(tag2); node.tags.add(tag2);
expect(node.hasTag(tag1), isTrue); expect(node.isTagged(tag1), isTrue);
expect(node.hasTag(tag2), isTrue); expect(node.isTagged(tag2), isTrue);
}); });
test('getSemanticsData includes tags', () { test('getSemanticsData includes tags', () {
final Set<SemanticsTag> tags = new Set<SemanticsTag>()
..add(tag1)
..add(tag2);
final SemanticsNode node = new SemanticsNode() final SemanticsNode node = new SemanticsNode()
..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 10.0) ..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 10.0)
..addTag(tag1) ..tags = tags;
..addTag(tag2);
final Set<SemanticsTag> expected = new Set<SemanticsTag>() expect(node.getSemanticsData().tags, tags);
..add(tag1)
..add(tag2); tags.add(tag3);
expect(node.getSemanticsData().tags, expected); final SemanticsConfiguration config = new SemanticsConfiguration()
..isMergingSemanticsOfDescendants = true;
node.mergeAllDescendantsIntoThisNode = true; node.updateWith(
node.addChildren(<SemanticsNode>[ config: config,
childrenInInversePaintOrder: <SemanticsNode>[
new SemanticsNode() new SemanticsNode()
..isMergedIntoParent = true
..rect = new Rect.fromLTRB(5.0, 5.0, 10.0, 10.0) ..rect = new Rect.fromLTRB(5.0, 5.0, 10.0, 10.0)
..addTag(tag3), ..tags = tags,
]); ],
node.finalizeChildren(); );
expected.add(tag3);
expect(node.getSemanticsData().tags, expected); expect(node.getSemanticsData().tags, tags);
}); });
test('after markNeedsSemanticsUpdate(onlyLocalUpdates: true) all render objects between two semantic boundaries are asked for annotations', () { test('after markNeedsSemanticsUpdate(onlyLocalUpdates: true) all render objects between two semantic boundaries are asked for annotations', () {
...@@ -88,7 +92,6 @@ void main() { ...@@ -88,7 +92,6 @@ void main() {
middle.action = SemanticsAction.scrollDown; middle.action = SemanticsAction.scrollDown;
middle.markNeedsSemanticsUpdate(onlyLocalUpdates: true); middle.markNeedsSemanticsUpdate(onlyLocalUpdates: true);
expect(root.debugSemantics.getSemanticsData().actions, 0); // SemanticsNode is reset
pumpFrame(phase: EnginePhase.flushSemantics); pumpFrame(phase: EnginePhase.flushSemantics);
...@@ -104,8 +107,10 @@ void main() { ...@@ -104,8 +107,10 @@ void main() {
..rect = new Rect.fromLTRB(5.0, 0.0, 10.0, 5.0); ..rect = new Rect.fromLTRB(5.0, 0.0, 10.0, 5.0);
final SemanticsNode root = new SemanticsNode() final SemanticsNode root = new SemanticsNode()
..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 5.0); ..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 5.0);
root.addChildren(<SemanticsNode>[child1, child2]); root.updateWith(
root.finalizeChildren(); config: null,
childrenInInversePaintOrder: <SemanticsNode>[child1, child2],
);
expect(root.transform, isNull); expect(root.transform, isNull);
expect(child1.transform, isNull); expect(child1.transform, isNull);
...@@ -126,8 +131,10 @@ void main() { ...@@ -126,8 +131,10 @@ void main() {
..rect = new Rect.fromLTRB(10.0, 0.0, 15.0, 5.0); ..rect = new Rect.fromLTRB(10.0, 0.0, 15.0, 5.0);
final SemanticsNode root = new SemanticsNode() final SemanticsNode root = new SemanticsNode()
..rect = new Rect.fromLTRB(0.0, 0.0, 20.0, 5.0); ..rect = new Rect.fromLTRB(0.0, 0.0, 20.0, 5.0);
root.addChildren(<SemanticsNode>[child1, child2]); root.updateWith(
root.finalizeChildren(); config: null,
childrenInInversePaintOrder: <SemanticsNode>[child1, child2],
);
expect( expect(
root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal), root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal),
'SemanticsNode#11(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n' 'SemanticsNode#11(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n'
...@@ -144,18 +151,22 @@ void main() { ...@@ -144,18 +151,22 @@ void main() {
final SemanticsNode child3 = new SemanticsNode() final SemanticsNode child3 = new SemanticsNode()
..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 5.0); ..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 5.0);
child3.addChildren(<SemanticsNode>[ child3.updateWith(
config: null,
childrenInInversePaintOrder: <SemanticsNode>[
new SemanticsNode() new SemanticsNode()
..rect = new Rect.fromLTRB(5.0, 0.0, 10.0, 5.0), ..rect = new Rect.fromLTRB(5.0, 0.0, 10.0, 5.0),
new SemanticsNode() new SemanticsNode()
..rect = new Rect.fromLTRB(0.0, 0.0, 5.0, 5.0), ..rect = new Rect.fromLTRB(0.0, 0.0, 5.0, 5.0),
]); ],
child3.finalizeChildren(); );
final SemanticsNode rootComplex = new SemanticsNode() final SemanticsNode rootComplex = new SemanticsNode()
..rect = new Rect.fromLTRB(0.0, 0.0, 25.0, 5.0); ..rect = new Rect.fromLTRB(0.0, 0.0, 25.0, 5.0);
rootComplex.addChildren(<SemanticsNode>[child1, child2, child3]); rootComplex.updateWith(
rootComplex.finalizeChildren(); config: null,
childrenInInversePaintOrder: <SemanticsNode>[child1, child2, child3]
);
expect( expect(
rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal), rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal),
...@@ -187,28 +198,30 @@ void main() { ...@@ -187,28 +198,30 @@ void main() {
expect( expect(
minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden), minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), wasAffectedByClip: false, actions: [], tags: [], isSelected: false, label: "", textDirection: null)\n', 'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), wasAffectedByClip: false, actions: [], isSelected: false, label: "", textDirection: null)\n',
); );
final SemanticsNode allProperties = new SemanticsNode() final SemanticsConfiguration config = new SemanticsConfiguration()
..rect = new Rect.fromLTWH(50.0, 10.0, 20.0, 30.0) ..isMergingSemanticsOfDescendants = true
..mergeAllDescendantsIntoThisNode = true ..addAction(SemanticsAction.scrollUp, () { })
..transform = new Matrix4.translation(new Vector3(10.0, 10.0, 0.0)) ..addAction(SemanticsAction.longPress, () { })
..wasAffectedByClip = true ..addAction(SemanticsAction.showOnScreen, () { })
..addAction(SemanticsAction.scrollUp)
..addAction(SemanticsAction.longPress)
..addAction(SemanticsAction.showOnScreen)
..isChecked = false ..isChecked = false
..isSelected = true ..isSelected = true
..label = "Use all the properties" ..label = "Use all the properties"
..textDirection = TextDirection.rtl; ..textDirection = TextDirection.rtl;
final SemanticsNode allProperties = new SemanticsNode()
..rect = new Rect.fromLTWH(50.0, 10.0, 20.0, 30.0)
..transform = new Matrix4.translation(new Vector3(10.0, 10.0, 0.0))
..wasAffectedByClip = true
..updateWith(config: config, childrenInInversePaintOrder: null);
expect( expect(
allProperties.toStringDeep(), allProperties.toStringDeep(),
'SemanticsNode#17(STALE, owner: null, leaf merge, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), clipped, actions: [longPress, scrollUp, showOnScreen], selected, label: "Use all the properties", textDirection: rtl)\n', 'SemanticsNode#17(STALE, owner: null, leaf merge, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), clipped, actions: [longPress, scrollUp, showOnScreen], unchecked, selected, label: "Use all the properties", textDirection: rtl)\n',
); );
expect( expect(
allProperties.getSemanticsData().toString(), allProperties.getSemanticsData().toString(),
'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: [isSelected], label: "Use all the properties", textDirection: rtl)', '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], label: "Use all the properties", textDirection: rtl)',
); );
final SemanticsNode scaled = new SemanticsNode() final SemanticsNode scaled = new SemanticsNode()
...@@ -223,37 +236,22 @@ void main() { ...@@ -223,37 +236,22 @@ void main() {
'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])', '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])',
); );
}); });
test('reset clears tags', () {
const SemanticsTag tag = const SemanticsTag('tag for testing');
final SemanticsNode node = new SemanticsNode();
expect(node.hasTag(tag), isFalse);
node.addTag(tag);
expect(node.hasTag(tag), isTrue);
node.reset();
expect(node.hasTag(tag), isFalse);
});
} }
class TestRender extends RenderProxyBox { class TestRender extends RenderProxyBox {
TestRender({ this.action, this.isSemanticBoundary, RenderObject child }) : super(child); TestRender({ this.action, this.isSemanticBoundary, RenderObject child }) : super(child);
@override
final bool isSemanticBoundary; final bool isSemanticBoundary;
SemanticsAction action; SemanticsAction action;
@override @override
SemanticsAnnotator get semanticsAnnotator => _annotate; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
void _annotate(SemanticsNode node) { config
node.addAction(action); ..isSemanticBoundary = isSemanticBoundary
..addAction(action, () { });
} }
} }
// 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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:test/test.dart';
import 'rendering_tester.dart';
void main() {
test('only send semantics update if semantics have changed', () {
final TestRender testRender = new TestRender()
..label = 'hello'
..textDirection = TextDirection.ltr;
final RenderObject tree = new RenderConstrainedBox(
additionalConstraints: const BoxConstraints.tightFor(height: 20.0, width: 20.0),
child: testRender,
);
int semanticsUpdateCount = 0;
final SemanticsHandle semanticsHandle = renderer.pipelineOwner.ensureSemantics(
listener: () {
++semanticsUpdateCount;
}
);
layout(tree, phase: EnginePhase.flushSemantics);
// Initial render does semantics.
expect(semanticsUpdateCount, 1);
expect(testRender.describeSemanticsConfigurationCallCount, isNot(0));
testRender.describeSemanticsConfigurationCallCount = 0;
semanticsUpdateCount = 0;
// Request semantics update even though nothing changed.
testRender.markNeedsSemanticsUpdate();
pumpFrame(phase: EnginePhase.flushSemantics);
// Object is asked for semantics, but no update is sent.
expect(semanticsUpdateCount, 0);
expect(testRender.describeSemanticsConfigurationCallCount, 1);
testRender.describeSemanticsConfigurationCallCount = 0;
semanticsUpdateCount = 0;
// Change semantics and request update.
testRender.label = 'bye';
testRender.markNeedsSemanticsUpdate();
pumpFrame(phase: EnginePhase.flushSemantics);
// Object is asked for semantics, and update is sent.
expect(semanticsUpdateCount, 1);
expect(testRender.describeSemanticsConfigurationCallCount, 1);
semanticsHandle.dispose();
});
}
class TestRender extends RenderSemanticsAnnotations {
int describeSemanticsConfigurationCallCount = 0;
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
describeSemanticsConfigurationCallCount += 1;
}
}
...@@ -141,7 +141,7 @@ void main() { ...@@ -141,7 +141,7 @@ void main() {
), ),
); );
expect(semantics, hasSemantics(new TestSemantics.root(label: 'a label'))); expect(semantics, includesNodeWith(label: 'a label'));
}); });
testWidgets('Null icon with semantic label', (WidgetTester tester) async { testWidgets('Null icon with semantic label', (WidgetTester tester) async {
...@@ -159,7 +159,7 @@ void main() { ...@@ -159,7 +159,7 @@ void main() {
), ),
); );
expect(semantics, hasSemantics(new TestSemantics.root(label: 'a label'))); expect(semantics, includesNodeWith(label: 'a label'));
}); });
testWidgets('Changing semantic label from null doesn\'t rebuild tree ', (WidgetTester tester) async { testWidgets('Changing semantic label from null doesn\'t rebuild tree ', (WidgetTester tester) async {
......
// 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 'dart:ui' show SemanticsFlags;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'semantics_tester.dart';
void main() {
testWidgets('Implicit Semantics merge behavior', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Semantics(
container: true,
explicitChildNodes: false,
child: new Column(
children: <Widget>[
const Text('Michael Goderbauer'),
const Text('goderbauer@google.com'),
],
),
),
),
);
// SemanticsNode#0()
// └SemanticsNode#1(label: "Michael Goderbauer\ngoderbauer@google.com", textDirection: ltr)
expect(
semantics,
hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
label: 'Michael Goderbauer\ngoderbauer@google.com',
),
],
),
ignoreRect: true,
ignoreTransform: true,
),
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Semantics(
container: true,
explicitChildNodes: true,
child: new Column(
children: <Widget>[
const Text('Michael Goderbauer'),
const Text('goderbauer@google.com'),
],
),
),
),
);
// SemanticsNode#0()
// └SemanticsNode#1()
// ├SemanticsNode#2(label: "Michael Goderbauer", textDirection: ltr)
// └SemanticsNode#3(label: "goderbauer@google.com", textDirection: ltr)
expect(
semantics,
hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
children: <TestSemantics>[
new TestSemantics(
id: 2,
label: 'Michael Goderbauer',
),
new TestSemantics(
id: 3,
label: 'goderbauer@google.com',
),
],
),
],
),
ignoreRect: true,
ignoreTransform: true,
),
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Semantics(
container: true,
explicitChildNodes: true,
child: new Semantics(
label: 'Signed in as',
child: new Column(
children: <Widget>[
const Text('Michael Goderbauer'),
const Text('goderbauer@google.com'),
],
),
),
),
),
);
// SemanticsNode#0()
// └SemanticsNode#1()
// └SemanticsNode#4(label: "Signed in as\nMichael Goderbauer\ngoderbauer@google.com", textDirection: ltr)
expect(
semantics,
hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
children: <TestSemantics>[
new TestSemantics(
id: 4,
label: 'Signed in as\nMichael Goderbauer\ngoderbauer@google.com',
),
],
),
],
),
ignoreRect: true,
ignoreTransform: true,
),
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Semantics(
container: true,
explicitChildNodes: false,
child: new Semantics(
label: 'Signed in as',
child: new Column(
children: <Widget>[
const Text('Michael Goderbauer'),
const Text('goderbauer@google.com'),
],
),
),
),
),
);
// SemanticsNode#0()
// └SemanticsNode#1(label: "Signed in as\nMichael Goderbauer\ngoderbauer@google.com", textDirection: ltr)
expect(
semantics,
hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
label: 'Signed in as\nMichael Goderbauer\ngoderbauer@google.com',
),
],
),
ignoreRect: true,
ignoreTransform: true,
),
);
semantics.dispose();
});
testWidgets('Do not merge with conflicts', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Semantics(
container: true,
explicitChildNodes: false,
child: new Column(
children: <Widget>[
new Semantics(
label: 'node 1',
selected: true,
child: new Container(
width: 10.0,
height: 10.0,
),
),
new Semantics(
label: 'node 2',
selected: true,
child: new Container(
width: 10.0,
height: 10.0,
),
),
new Semantics(
label: 'node 3',
selected: true,
child: new Container(
width: 10.0,
height: 10.0,
),
),
],
),
),
),
);
// SemanticsNode#0()
// └SemanticsNode#8()
// ├SemanticsNode#5(selected, label: "node 1", textDirection: ltr)
// ├SemanticsNode#6(selected, label: "node 2", textDirection: ltr)
// └SemanticsNode#7(selected, label: "node 3", textDirection: ltr)
expect(
semantics,
hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 8,
children: <TestSemantics>[
new TestSemantics(
id: 5,
flags: SemanticsFlags.isSelected.index,
label: 'node 1',
),
new TestSemantics(
id: 6,
flags: SemanticsFlags.isSelected.index,
label: 'node 2',
),
new TestSemantics(
id: 7,
flags: SemanticsFlags.isSelected.index,
label: 'node 3',
),
],
),
],
),
ignoreRect: true,
ignoreTransform: true,
),
);
semantics.dispose();
});
}
...@@ -104,11 +104,11 @@ void main() { ...@@ -104,11 +104,11 @@ void main() {
final TestSemantics expectedSemantics = new TestSemantics.root( final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 1, id: 2,
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 2, id: 1,
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
actions: SemanticsAction.tap.index, actions: SemanticsAction.tap.index,
), ),
......
...@@ -94,12 +94,19 @@ class TestWidget extends SingleChildRenderObjectWidget { ...@@ -94,12 +94,19 @@ class TestWidget extends SingleChildRenderObjectWidget {
} }
class RenderTest extends RenderProxyBox { class RenderTest extends RenderProxyBox {
@override @override
SemanticsAnnotator get semanticsAnnotator => isSemanticBoundary ? _annotate : null; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
if (!_isSemanticBoundary)
return;
config
..isSemanticBoundary = _isSemanticBoundary
..label = _label
..textDirection = TextDirection.ltr;
void _annotate(SemanticsNode node) {
node.label = _label;
node.textDirection = TextDirection.ltr;
} }
String _label; String _label;
...@@ -111,8 +118,7 @@ class RenderTest extends RenderProxyBox { ...@@ -111,8 +118,7 @@ class RenderTest extends RenderProxyBox {
callLog.add('markNeedsSemanticsUpdate(onlyChanges: true)'); callLog.add('markNeedsSemanticsUpdate(onlyChanges: true)');
} }
@override
bool get isSemanticBoundary => _isSemanticBoundary;
bool _isSemanticBoundary; bool _isSemanticBoundary;
set isSemanticBoundary(bool value) { set isSemanticBoundary(bool value) {
if (_isSemanticBoundary == value) if (_isSemanticBoundary == value)
......
// 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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'semantics_tester.dart';
void main() {
testWidgets('markNeedsSemanticsUpdate allways resets node', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(const TestWidget());
final RenderTest renderObj = tester.renderObject(find.byType(TestWidget));
expect(renderObj.labelWasReset, hasLength(1));
expect(renderObj.labelWasReset.last, true);
expect(semantics, includesNodeWith(label: 'Label 1'));
renderObj.markNeedsSemanticsUpdate(onlyLocalUpdates: false, noGeometry: false);
await tester.pumpAndSettle();
expect(renderObj.labelWasReset, hasLength(2));
expect(renderObj.labelWasReset.last, true);
expect(semantics, includesNodeWith(label: 'Label 2'));
renderObj.markNeedsSemanticsUpdate(onlyLocalUpdates: true, noGeometry: false);
await tester.pumpAndSettle();
expect(renderObj.labelWasReset, hasLength(3));
expect(renderObj.labelWasReset.last, true);
expect(semantics, includesNodeWith(label: 'Label 3'));
renderObj.markNeedsSemanticsUpdate(onlyLocalUpdates: true, noGeometry: true);
await tester.pumpAndSettle();
expect(renderObj.labelWasReset, hasLength(4));
expect(renderObj.labelWasReset.last, true);
expect(semantics, includesNodeWith(label: 'Label 4'));
renderObj.markNeedsSemanticsUpdate(onlyLocalUpdates: false, noGeometry: true);
await tester.pumpAndSettle();
expect(renderObj.labelWasReset, hasLength(5));
expect(renderObj.labelWasReset.last, true);
expect(semantics, includesNodeWith(label: 'Label 5'));
semantics.dispose();
});
}
class TestWidget extends SingleChildRenderObjectWidget {
const TestWidget({
Key key,
Widget child,
}) : super(key: key, child: child);
@override
RenderTest createRenderObject(BuildContext context) {
return new RenderTest();
}
}
class RenderTest extends RenderProxyBox {
List<bool> labelWasReset = <bool>[];
@override
SemanticsAnnotator get semanticsAnnotator => _annotate;
void _annotate(SemanticsNode node) {
labelWasReset.add(node.label == '');
node.label = 'Label ${labelWasReset.length}';
node.textDirection = TextDirection.ltr;
}
}
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' show SemanticsFlags;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -15,136 +17,221 @@ void main() { ...@@ -15,136 +17,221 @@ void main() {
// smoketest // smoketest
await tester.pumpWidget( await tester.pumpWidget(
new Container( new Semantics(
container: true,
child: new Container(
child: new Semantics( child: new Semantics(
label: 'test1', label: 'test1',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Container() child: new Container(),
) selected: true,
) ),
),
),
); );
expect(semantics, hasSemantics(new TestSemantics.root(label: 'test1'))); expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
label: 'test1',
rect: TestSemantics.fullScreen,
flags: SemanticsFlags.isSelected.index,
)
]
)));
// control for forking // control for forking
await tester.pumpWidget( await tester.pumpWidget(
new Column( new Semantics(
container: true,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
new Container( new Container(
height: 10.0, height: 10.0,
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr), child: const Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
),
), ),
new Container( new Container(
height: 10.0, height: 10.0,
child: const IgnorePointer( child: const IgnorePointer(
ignoring: true, ignoring: true,
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr), child: const Semantics(
) label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
),
),
), ),
], ],
) ),
),
); );
expect(semantics, hasSemantics(new TestSemantics.root(label: 'child1'))); expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
label: 'child1',
rect: TestSemantics.fullScreen,
flags: SemanticsFlags.isSelected.index,
)
],
)));
// forking semantics // forking semantics
await tester.pumpWidget( await tester.pumpWidget(
new Column( new Semantics(
container: true,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
new Container( new Container(
height: 10.0, height: 10.0,
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr), child: const Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
),
), ),
new Container( new Container(
height: 10.0, height: 10.0,
child: const IgnorePointer( child: const IgnorePointer(
ignoring: false, ignoring: false,
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr), child: const Semantics(
) label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
),
),
), ),
], ],
) ),
),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(new TestSemantics.root(
new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 1, id: 1,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 2,
label: 'child1', label: 'child1',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlags.isSelected.index,
), ),
new TestSemantics.rootChild( new TestSemantics(
id: 2, id: 3,
label: 'child2', label: 'child2',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
transform: new Matrix4.translationValues(0.0, 10.0, 0.0), flags: SemanticsFlags.isSelected.index,
), ),
], ],
) ),
)); ],
), ignoreTransform: true));
// toggle a branch off // toggle a branch off
await tester.pumpWidget( await tester.pumpWidget(
new Column( new Semantics(
container: true,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
new Container( new Container(
height: 10.0, height: 10.0,
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr) child: const Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
),
), ),
new Container( new Container(
height: 10.0, height: 10.0,
child: const IgnorePointer( child: const IgnorePointer(
ignoring: true, ignoring: true,
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr) child: const Semantics(
) label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
),
),
), ),
], ],
) ),
),
); );
expect(semantics, hasSemantics(new TestSemantics.root(label: 'child1'))); expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
label: 'child1',
rect: TestSemantics.fullScreen,
flags: SemanticsFlags.isSelected.index,
)
],
)));
// toggle a branch back on // toggle a branch back on
await tester.pumpWidget( await tester.pumpWidget(
new Column( new Semantics(
container: true,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
new Container( new Container(
height: 10.0, height: 10.0,
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr) child: const Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
),
), ),
new Container( new Container(
height: 10.0, height: 10.0,
child: const IgnorePointer( child: const IgnorePointer(
ignoring: false, ignoring: false,
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr) child: const Semantics(
) label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
),
),
), ),
], ],
crossAxisAlignment: CrossAxisAlignment.stretch ),
) ),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(new TestSemantics.root(
new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 3, id: 1,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 4,
label: 'child1', label: 'child1',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlags.isSelected.index,
), ),
new TestSemantics.rootChild( new TestSemantics(
id: 2, id: 3,
label: 'child2', label: 'child2',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
transform: new Matrix4.translationValues(0.0, 10.0, 0.0), flags: SemanticsFlags.isSelected.index,
), ),
], ],
) ),
)); ],
), ignoreTransform: true));
semantics.dispose(); semantics.dispose();
}); });
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' show SemanticsFlags;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -19,105 +21,153 @@ void main() { ...@@ -19,105 +21,153 @@ void main() {
// forking semantics // forking semantics
await tester.pumpWidget( await tester.pumpWidget(
new Column( new Semantics(
container: true,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
new Container( new Container(
height: 10.0, height: 10.0,
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr) child: const Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
),
), ),
new Container( new Container(
height: 10.0, height: 10.0,
child: const IgnorePointer( child: const IgnorePointer(
ignoring: false, ignoring: false,
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr) child: const Semantics(
) label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
),
),
), ),
], ],
crossAxisAlignment: CrossAxisAlignment.stretch ),
) ),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(new TestSemantics.root(
new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 3,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 1, id: 1,
label: 'child1', label: 'child1',
textDirection: TextDirection.ltr,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlags.isSelected.index,
), ),
new TestSemantics.rootChild( new TestSemantics(
id: 2, id: 2,
label: 'child2', label: 'child2',
textDirection: TextDirection.ltr,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
transform: new Matrix4.translationValues(0.0, 10.0, 0.0), flags: SemanticsFlags.isSelected.index,
), ),
], ],
) ),
)); ],
), ignoreTransform: true));
// toggle a branch off // toggle a branch off
await tester.pumpWidget( await tester.pumpWidget(
new Column( new Semantics(
container: true,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
new Container( new Container(
height: 10.0, height: 10.0,
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr) child: const Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
),
), ),
new Container( new Container(
height: 10.0, height: 10.0,
child: const IgnorePointer( child: const IgnorePointer(
ignoring: true, ignoring: true,
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr) child: const Semantics(
) label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
),
),
), ),
], ],
crossAxisAlignment: CrossAxisAlignment.stretch ),
) ),
); );
expect(semantics, hasSemantics(new TestSemantics.root(label: 'child1'))); expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 3,
label: 'child1',
rect: TestSemantics.fullScreen,
flags: SemanticsFlags.isSelected.index,
)
],
)));
// toggle a branch back on // toggle a branch back on
await tester.pumpWidget( await tester.pumpWidget(
new Column( new Semantics(
container: true,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
new Container( new Container(
height: 10.0, height: 10.0,
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr) child: const Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
),
), ),
new Container( new Container(
height: 10.0, height: 10.0,
child: const IgnorePointer( child: const IgnorePointer(
ignoring: false, ignoring: false,
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr) child: const Semantics(
) label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
),
),
), ),
], ],
crossAxisAlignment: CrossAxisAlignment.stretch ),
) ),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(new TestSemantics.root(
new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 3, id: 3,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 4,
label: 'child1', label: 'child1',
textDirection: TextDirection.ltr,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlags.isSelected.index,
), ),
new TestSemantics.rootChild( new TestSemantics(
id: 2, id: 2,
label: 'child2', label: 'child2',
textDirection: TextDirection.ltr,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
transform: new Matrix4.translationValues(0.0, 10.0, 0.0), flags: SemanticsFlags.isSelected.index,
), ),
], ],
) ),
)); ],
), ignoreTransform: true));
semantics.dispose(); semantics.dispose();
}); });
......
...@@ -16,81 +16,112 @@ void main() { ...@@ -16,81 +16,112 @@ void main() {
// implicit annotators // implicit annotators
await tester.pumpWidget( await tester.pumpWidget(
new Container( new Semantics(
container: true,
child: new Container(
child: new Semantics( child: new Semantics(
label: 'test', label: 'test',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Container( child: new Container(
child: const Semantics( child: const Semantics(
checked: true checked: true
) ),
) ),
) ),
) ),
),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
label: 'test', label: 'test',
rect: TestSemantics.fullScreen,
)
]
) )
)); ));
// remove one // remove one
await tester.pumpWidget( await tester.pumpWidget(
new Container( new Semantics(
container: true,
child: new Container( child: new Container(
child: const Semantics( child: const Semantics(
checked: true checked: true,
) ),
) ),
) ),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
rect: TestSemantics.fullScreen,
),
]
) )
)); ));
// change what it says // change what it says
await tester.pumpWidget( await tester.pumpWidget(
new Container( new Semantics(
container: true,
child: new Container( child: new Container(
child: const Semantics( child: const Semantics(
label: 'test', label: 'test',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
) ),
) ),
) ),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
label: 'test', label: 'test',
textDirection: TextDirection.ltr,
rect: TestSemantics.fullScreen,
),
]
) )
)); ));
// add a node // add a node
await tester.pumpWidget( await tester.pumpWidget(
new Container( new Semantics(
child: new Semantics( container: true,
checked: true,
child: new Container( child: new Container(
child: const Semantics(
checked: true,
child: const Semantics( child: const Semantics(
label: 'test', label: 'test',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
) ),
) ),
) ),
) ),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
label: 'test', label: 'test',
rect: TestSemantics.fullScreen,
) )
],
),
)); ));
int changeCount = 0; int changeCount = 0;
...@@ -100,17 +131,18 @@ void main() { ...@@ -100,17 +131,18 @@ void main() {
// make no changes // make no changes
await tester.pumpWidget( await tester.pumpWidget(
new Container( new Semantics(
child: new Semantics( container: true,
checked: true,
child: new Container( child: new Container(
child: const Semantics(
checked: true,
child: const Semantics( child: const Semantics(
label: 'test', label: 'test',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
) ),
) ),
) ),
) ),
); );
expect(changeCount, 0); expect(changeCount, 0);
......
...@@ -6,6 +6,7 @@ import 'dart:ui' show SemanticsFlags; ...@@ -6,6 +6,7 @@ import 'dart:ui' show SemanticsFlags;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'semantics_tester.dart'; import 'semantics_tester.dart';
...@@ -26,10 +27,12 @@ void main() { ...@@ -26,10 +27,12 @@ void main() {
fit: StackFit.expand, fit: StackFit.expand,
children: <Widget>[ children: <Widget>[
const Semantics( const Semantics(
container: true,
label: 'L1', label: 'L1',
), ),
new Semantics( new Semantics(
label: 'L2', label: 'L2',
container: true,
child: new Stack( child: new Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: <Widget>[ children: <Widget>[
...@@ -50,22 +53,22 @@ void main() { ...@@ -50,22 +53,22 @@ void main() {
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 1, id: 3,
label: 'L1', label: 'L1',
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
), ),
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 2, id: 4,
label: 'L2', label: 'L2',
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 3, id: 1,
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
), ),
new TestSemantics( new TestSemantics(
id: 4, id: 2,
flags: SemanticsFlags.hasCheckedState.index, flags: SemanticsFlags.hasCheckedState.index,
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
), ),
...@@ -87,9 +90,11 @@ void main() { ...@@ -87,9 +90,11 @@ void main() {
children: <Widget>[ children: <Widget>[
const Semantics( const Semantics(
label: 'L1', label: 'L1',
container: true,
), ),
new Semantics( new Semantics(
label: 'L2', label: 'L2',
container: true,
child: new Stack( child: new Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: <Widget>[ children: <Widget>[
...@@ -108,12 +113,12 @@ void main() { ...@@ -108,12 +113,12 @@ void main() {
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 1, id: 3,
label: 'L1', label: 'L1',
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
), ),
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 2, id: 4,
label: 'L2', label: 'L2',
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
...@@ -134,6 +139,7 @@ void main() { ...@@ -134,6 +139,7 @@ void main() {
const Semantics(), const Semantics(),
new Semantics( new Semantics(
label: 'L2', label: 'L2',
container: true,
child: new Stack( child: new Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: <Widget>[ children: <Widget>[
...@@ -150,8 +156,14 @@ void main() { ...@@ -150,8 +156,14 @@ void main() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 4,
label: 'L2', label: 'L2',
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
rect: TestSemantics.fullScreen,
),
],
) )
)); ));
......
...@@ -55,7 +55,7 @@ void main() { ...@@ -55,7 +55,7 @@ void main() {
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 1, id: 3,
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
label: label, label: label,
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
...@@ -111,7 +111,7 @@ void main() { ...@@ -111,7 +111,7 @@ void main() {
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 1, id: 3,
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
label: label, label: label,
rect: TestSemantics.fullScreen, rect: TestSemantics.fullScreen,
......
...@@ -39,9 +39,15 @@ void main() { ...@@ -39,9 +39,15 @@ void main() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 3,
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
label: 'label', label: 'label',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
rect: TestSemantics.fullScreen,
)
]
) )
)); ));
...@@ -71,10 +77,16 @@ void main() { ...@@ -71,10 +77,16 @@ void main() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 3,
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
label: 'label', label: 'label',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
rect: TestSemantics.fullScreen,
) )
],
),
)); ));
semantics.dispose(); semantics.dispose();
......
...@@ -61,6 +61,7 @@ void main() { ...@@ -61,6 +61,7 @@ void main() {
new Semantics( new Semantics(
label: '#2', label: '#2',
container: true, container: true,
explicitChildNodes: true,
child: new Stack( child: new Stack(
children: <Widget>[ children: <Widget>[
new Semantics( new Semantics(
...@@ -146,9 +147,12 @@ class RenderBoundaryBlockSemantics extends RenderProxyBox { ...@@ -146,9 +147,12 @@ class RenderBoundaryBlockSemantics extends RenderProxyBox {
RenderBoundaryBlockSemantics({ RenderBox child }) : super(child); RenderBoundaryBlockSemantics({ RenderBox child }) : super(child);
@override @override
bool get isBlockingSemanticsOfPreviouslyPaintedNodes => true; void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
@override config
bool get isSemanticBoundary => true; ..isBlockingSemanticsOfPreviouslyPaintedNodes = true
..isSemanticBoundary = true;
}
} }
...@@ -62,15 +62,9 @@ void main() { ...@@ -62,15 +62,9 @@ void main() {
final SemanticsNode node1 = tester.renderObject(find.byWidget(const Text('1'))).debugSemantics; final SemanticsNode node1 = tester.renderObject(find.byWidget(const Text('1'))).debugSemantics;
final SemanticsNode node2 = tester.renderObject(find.byWidget(const Text('2'))).debugSemantics; final SemanticsNode node2 = tester.renderObject(find.byWidget(const Text('2'))).debugSemantics;
final SemanticsNode node3 = tester.renderObject(find.byWidget(const Text('3'))).debugSemantics;
expect(node1.wasAffectedByClip, false); expect(node1.wasAffectedByClip, false);
expect(node2.wasAffectedByClip, true); expect(node2.wasAffectedByClip, true);
expect(node3.wasAffectedByClip, true);
expect(node1.isInvisible, isFalse);
expect(node2.isInvisible, isFalse);
expect(node3.isInvisible, isTrue);
semantics.dispose(); semantics.dispose();
}); });
...@@ -117,12 +111,12 @@ void main() { ...@@ -117,12 +111,12 @@ void main() {
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 4, id: 3,
label: '1', label: '1',
rect: new Rect.fromLTRB(0.0, 0.0, 75.0, 14.0), rect: new Rect.fromLTRB(0.0, 0.0, 75.0, 14.0),
), ),
new TestSemantics( new TestSemantics(
id: 5, id: 4,
label: '2\n3', label: '2\n3',
rect: new Rect.fromLTRB(0.0, 0.0, 25.0, 14.0), // clipped form original 75.0 to 25.0 rect: new Rect.fromLTRB(0.0, 0.0, 25.0, 14.0), // clipped form original 75.0 to 25.0
), ),
......
...@@ -53,7 +53,14 @@ void main() { ...@@ -53,7 +53,14 @@ void main() {
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root(label: 'test1\ntest2'), new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 3,
label: 'test1\ntest2',
),
]
),
ignoreRect: true, ignoreRect: true,
ignoreTransform: true, ignoreTransform: true,
)); ));
...@@ -74,8 +81,8 @@ void main() { ...@@ -74,8 +81,8 @@ void main() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild(id: 5, label: 'test1'), new TestSemantics.rootChild(id: 4, label: 'test1'),
new TestSemantics.rootChild(id: 6, label: 'test2'), new TestSemantics.rootChild(id: 5, label: 'test2'),
], ],
), ),
ignoreRect: true, ignoreRect: true,
......
...@@ -13,8 +13,12 @@ void main() { ...@@ -13,8 +13,12 @@ void main() {
SemanticsTester semantics = new SemanticsTester(tester); SemanticsTester semantics = new SemanticsTester(tester);
final TestSemantics expectedSemantics = new TestSemantics.root( final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
label: 'test1', label: 'test1',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
)
],
); );
await tester.pumpWidget( await tester.pumpWidget(
...@@ -27,7 +31,12 @@ void main() { ...@@ -27,7 +31,12 @@ void main() {
) )
); );
expect(semantics, hasSemantics(expectedSemantics)); expect(semantics, hasSemantics(
expectedSemantics,
ignoreTransform: true,
ignoreRect: true,
ignoreId: true,
));
semantics.dispose(); semantics.dispose();
semantics = null; semantics = null;
...@@ -37,7 +46,12 @@ void main() { ...@@ -37,7 +46,12 @@ void main() {
expect(tester.binding.hasScheduledFrame, isTrue); expect(tester.binding.hasScheduledFrame, isTrue);
await tester.pump(); await tester.pump();
expect(semantics, hasSemantics(expectedSemantics)); expect(semantics, hasSemantics(
expectedSemantics,
ignoreTransform: true,
ignoreRect: true,
ignoreId: true,
));
semantics.dispose(); semantics.dispose();
}); });
...@@ -62,15 +76,20 @@ void main() { ...@@ -62,15 +76,20 @@ void main() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
label: 'test1',
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 1, label: 'test1',
children: <TestSemantics>[
new TestSemantics(
label: 'test2a', label: 'test2a',
rect: TestSemantics.fullScreen,
) )
] ]
) )
]
),
ignoreId: true,
ignoreRect: true,
ignoreTransform: true,
)); ));
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Directionality(
...@@ -94,22 +113,25 @@ void main() { ...@@ -94,22 +113,25 @@ void main() {
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
label: 'test1',
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics.rootChild( new TestSemantics.rootChild(
id: 2, label: 'test1',
children: <TestSemantics>[
new TestSemantics(
label: 'middle', label: 'middle',
rect: TestSemantics.fullScreen,
children: <TestSemantics>[ children: <TestSemantics>[
new TestSemantics( new TestSemantics(
id: 1,
label: 'test2b', label: 'test2b',
rect: TestSemantics.fullScreen, ),
],
) )
] ]
) )
] ]
) ),
ignoreId: true,
ignoreRect: true,
ignoreTransform: true,
)); ));
semantics.dispose(); semantics.dispose();
...@@ -118,11 +140,6 @@ void main() { ...@@ -118,11 +140,6 @@ void main() {
testWidgets('Semantics and Directionality - RTL', (WidgetTester tester) async { testWidgets('Semantics and Directionality - RTL', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
final TestSemantics expectedSemantics = new TestSemantics.root(
label: 'test1',
textDirection: TextDirection.rtl,
);
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new Directionality(
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
...@@ -133,17 +150,12 @@ void main() { ...@@ -133,17 +150,12 @@ void main() {
), ),
); );
expect(semantics, hasSemantics(expectedSemantics)); expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.rtl));
}); });
testWidgets('Semantics and Directionality - LTR', (WidgetTester tester) async { testWidgets('Semantics and Directionality - LTR', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
final TestSemantics expectedSemantics = new TestSemantics.root(
label: 'test1',
textDirection: TextDirection.ltr,
);
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -154,15 +166,19 @@ void main() { ...@@ -154,15 +166,19 @@ void main() {
), ),
); );
expect(semantics, hasSemantics(expectedSemantics)); expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.ltr));
}); });
testWidgets('Semantics and Directionality - overriding RTL with LTR', (WidgetTester tester) async { testWidgets('Semantics and Directionality - cannot override RTL with LTR', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
final TestSemantics expectedSemantics = new TestSemantics.root( final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
label: 'test1', label: 'test1',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
)
]
); );
await tester.pumpWidget( await tester.pumpWidget(
...@@ -176,15 +192,19 @@ void main() { ...@@ -176,15 +192,19 @@ void main() {
), ),
); );
expect(semantics, hasSemantics(expectedSemantics)); expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
}); });
testWidgets('Semantics and Directionality - overriding LTR with RTL', (WidgetTester tester) async { testWidgets('Semantics and Directionality - cannot override LTR with RTL', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
final TestSemantics expectedSemantics = new TestSemantics.root( final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
label: 'test1', label: 'test1',
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
)
]
); );
await tester.pumpWidget( await tester.pumpWidget(
...@@ -198,6 +218,6 @@ void main() { ...@@ -198,6 +218,6 @@ void main() {
), ),
); );
expect(semantics, hasSemantics(expectedSemantics)); expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
}); });
} }
...@@ -31,7 +31,7 @@ class TestSemantics { ...@@ -31,7 +31,7 @@ class TestSemantics {
/// * [TestSemantics.fullScreen] 800x600, the test screen's size in logical /// * [TestSemantics.fullScreen] 800x600, the test screen's size in logical
/// pixels, useful for other full-screen widgets. /// pixels, useful for other full-screen widgets.
TestSemantics({ TestSemantics({
@required this.id, this.id,
this.flags: 0, this.flags: 0,
this.actions: 0, this.actions: 0,
this.label: '', this.label: '',
...@@ -40,8 +40,7 @@ class TestSemantics { ...@@ -40,8 +40,7 @@ class TestSemantics {
this.transform, this.transform,
this.children: const <TestSemantics>[], this.children: const <TestSemantics>[],
Iterable<SemanticsTag> tags, Iterable<SemanticsTag> tags,
}) : assert(id != null), }) : assert(flags != null),
assert(flags != null),
assert(label != null), assert(label != null),
assert(children != null), assert(children != null),
tags = tags?.toSet() ?? new Set<SemanticsTag>(); tags = tags?.toSet() ?? new Set<SemanticsTag>();
...@@ -73,7 +72,7 @@ class TestSemantics { ...@@ -73,7 +72,7 @@ class TestSemantics {
/// [TestSemantics.fullScreen] property may be useful as a value; it describes /// [TestSemantics.fullScreen] property may be useful as a value; it describes
/// an 800x600 rectangle, which is the test screen's size in logical pixels. /// an 800x600 rectangle, which is the test screen's size in logical pixels.
TestSemantics.rootChild({ TestSemantics.rootChild({
@required this.id, this.id,
this.flags: 0, this.flags: 0,
this.actions: 0, this.actions: 0,
this.label: '', this.label: '',
...@@ -152,7 +151,7 @@ class TestSemantics { ...@@ -152,7 +151,7 @@ class TestSemantics {
/// The tags of this node. /// The tags of this node.
final Set<SemanticsTag> tags; final Set<SemanticsTag> tags;
bool _matches(SemanticsNode node, Map<dynamic, dynamic> matchState, { bool ignoreRect: false, bool ignoreTransform: false }) { bool _matches(SemanticsNode node, Map<dynamic, dynamic> matchState, { bool ignoreRect: false, bool ignoreTransform: false, bool ignoreId: false }) {
final SemanticsData nodeData = node.getSemanticsData(); final SemanticsData nodeData = node.getSemanticsData();
bool fail(String message) { bool fail(String message) {
...@@ -162,7 +161,7 @@ class TestSemantics { ...@@ -162,7 +161,7 @@ class TestSemantics {
if (node == null) if (node == null)
return fail('could not find node with id $id.'); return fail('could not find node with id $id.');
if (id != node.id) if (!ignoreId && id != node.id)
return fail('expected node id $id but found id ${node.id}.'); return fail('expected node id $id but found id ${node.id}.');
if (flags != nodeData.flags) if (flags != nodeData.flags)
return fail('expected node id $id to have flags $flags but found flags ${nodeData.flags}.'); return fail('expected node id $id to have flags $flags but found flags ${nodeData.flags}.');
...@@ -188,7 +187,7 @@ class TestSemantics { ...@@ -188,7 +187,7 @@ class TestSemantics {
final Iterator<TestSemantics> it = children.iterator; final Iterator<TestSemantics> it = children.iterator;
node.visitChildren((SemanticsNode node) { node.visitChildren((SemanticsNode node) {
it.moveNext(); it.moveNext();
if (!it.current._matches(node, matchState, ignoreRect: ignoreRect, ignoreTransform: ignoreTransform)) { if (!it.current._matches(node, matchState, ignoreRect: ignoreRect, ignoreTransform: ignoreTransform, ignoreId: ignoreId)) {
result = false; result = false;
return false; return false;
} }
...@@ -233,15 +232,16 @@ class SemanticsTester { ...@@ -233,15 +232,16 @@ class SemanticsTester {
} }
class _HasSemantics extends Matcher { class _HasSemantics extends Matcher {
const _HasSemantics(this._semantics, { this.ignoreRect: false, this.ignoreTransform: false }) : assert(_semantics != null), assert(ignoreRect != null), assert(ignoreTransform != null); const _HasSemantics(this._semantics, { this.ignoreRect: false, this.ignoreTransform: false, this.ignoreId: false }) : assert(_semantics != null), assert(ignoreRect != null), assert(ignoreId != null), assert(ignoreTransform != null);
final TestSemantics _semantics; final TestSemantics _semantics;
final bool ignoreRect; final bool ignoreRect;
final bool ignoreTransform; final bool ignoreTransform;
final bool ignoreId;
@override @override
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) { bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
return _semantics._matches(item.tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode, matchState, ignoreTransform: ignoreTransform, ignoreRect: ignoreRect); return _semantics._matches(item.tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode, matchState, ignoreTransform: ignoreTransform, ignoreRect: ignoreRect, ignoreId: ignoreId);
} }
@override @override
...@@ -259,7 +259,8 @@ class _HasSemantics extends Matcher { ...@@ -259,7 +259,8 @@ class _HasSemantics extends Matcher {
Matcher hasSemantics(TestSemantics semantics, { Matcher hasSemantics(TestSemantics semantics, {
bool ignoreRect: false, bool ignoreRect: false,
bool ignoreTransform: false, bool ignoreTransform: false,
}) => new _HasSemantics(semantics, ignoreRect: ignoreRect, ignoreTransform: ignoreTransform); bool ignoreId: false,
}) => new _HasSemantics(semantics, ignoreRect: ignoreRect, ignoreTransform: ignoreTransform, ignoreId: ignoreId);
class _IncludesNodeWith extends Matcher { class _IncludesNodeWith extends Matcher {
const _IncludesNodeWith({ const _IncludesNodeWith({
......
// 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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'semantics_tester.dart';
void main() {
testWidgets('Simple tree is simple', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
const Center(
child: const Text('Hello!', textDirection: TextDirection.ltr)
),
);
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
label: 'Hello!',
textDirection: TextDirection.ltr,
rect: new Rect.fromLTRB(0.0, 0.0, 84.0, 14.0),
transform: new Matrix4.translationValues(358.0, 293.0, 0.0),
)
],
)));
semantics.dispose();
});
testWidgets('Simple tree is simple - material', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
// Not using Text widget because of https://github.com/flutter/flutter/issues/12357.
await tester.pumpWidget(new MaterialApp(
home: new Center(
child: new Semantics(
label: 'Hello!',
child: new Container(
width: 10.0,
height: 10.0,
),
),
),
));
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
label: 'Hello!',
textDirection: TextDirection.ltr,
rect: new Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
transform: new Matrix4.translationValues(395.0, 295.0, 0.0),
)
],
)));
semantics.dispose();
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment