Unverified Commit 9c49255f authored by Yegor's avatar Yegor Committed by GitHub

a11y: remove SemanticsSortOrder; sort locally only; semanticsOwner post-test check (#15537)

* a11y: remove SemanticsSortOrder; sort locally only; semanticsOwner post-test check

* update accessibility test framework

- default nextNodeId/previousNodeId to -1
- stop treating null as opt-out from value testing
- add `id`, `TestSemantics.root`, and `tags` to the suggested code in the TestSemantics failure message
- fix a small bug with raw string escaping
- update all tests accordingly

* fix sortKey doc

* prefer const over final
parent b494e71e
......@@ -3025,7 +3025,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
String decreasedValue,
String hint,
TextDirection textDirection,
SemanticsSortOrder sortOrder,
SemanticsSortKey sortKey,
VoidCallback onTap,
VoidCallback onLongPress,
VoidCallback onScrollLeft,
......@@ -3059,7 +3059,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
_decreasedValue = decreasedValue,
_hint = hint,
_textDirection = textDirection,
_sortOrder = sortOrder,
_sortKey = sortKey,
_onTap = onTap,
_onLongPress = onLongPress,
_onScrollLeft = onScrollLeft,
......@@ -3276,17 +3276,17 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate();
}
/// Sets the [SemanticsNode.sortOrder] to the given value.
/// Sets the [SemanticsNode.sortKey] to the given value.
///
/// This defines how this node will be sorted with the other semantics nodes
/// This defines how this node is sorted among the sibling semantics nodes
/// to determine the order in which they are traversed by the accessibility
/// services on the platform (e.g. VoiceOver on iOS and TalkBack on Android).
SemanticsSortOrder get sortOrder => _sortOrder;
SemanticsSortOrder _sortOrder;
set sortOrder(SemanticsSortOrder value) {
if (sortOrder == value)
SemanticsSortKey get sortKey => _sortKey;
SemanticsSortKey _sortKey;
set sortKey(SemanticsSortKey value) {
if (sortKey == value)
return;
_sortOrder = value;
_sortKey = value;
markNeedsSemanticsUpdate();
}
......@@ -3650,8 +3650,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
config.hint = hint;
if (textDirection != null)
config.textDirection = textDirection;
if (sortOrder != null)
config.sortOrder = sortOrder;
if (sortKey != null)
config.sortKey = sortKey;
// Registering _perform* as action handlers instead of the user provided
// ones to ensure that changing a user provided handler from a non-null to
// another non-null value doesn't require a semantics update.
......
......@@ -325,7 +325,7 @@ class SemanticsProperties extends DiagnosticableTree {
this.decreasedValue,
this.hint,
this.textDirection,
this.sortOrder,
this.sortKey,
this.onTap,
this.onLongPress,
this.onScrollLeft,
......@@ -465,22 +465,13 @@ class SemanticsProperties extends DiagnosticableTree {
/// Defaults to the ambient [Directionality].
final TextDirection textDirection;
/// Provides a traversal sorting order for this [Semantics] node.
/// Determines the position of this node among its siblings in the traversal
/// sort order.
///
/// This is used to describe the order in which the semantic node should be
/// traversed by the accessibility services on the platform (e.g. VoiceOver
/// on iOS and TalkBack on Android).
///
/// If [sortOrder.discardParentOrder] is false (the default), [sortOrder]'s
/// sort keys are appended to the list of keys from any ancestor nodes into a
/// list of [SemanticsSortKey]s that are compared in pairwise order.
/// Otherwise, it ignores the ancestor's [sortOrder] on this node.
///
/// See also:
///
/// * [SemanticsSortOrder] which provides a way to specify the order in
/// which semantic nodes are sorted.
final SemanticsSortOrder sortOrder;
final SemanticsSortKey sortKey;
/// The handler for [SemanticsAction.tap].
///
......@@ -679,7 +670,7 @@ class SemanticsProperties extends DiagnosticableTree {
description.add(new StringProperty('value', value));
description.add(new StringProperty('hint', hint));
description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
description.add(new DiagnosticsProperty<SemanticsSortOrder>('sortOrder', sortOrder, defaultValue: null));
description.add(new DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
}
}
......@@ -1034,7 +1025,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_increasedValue != config.increasedValue ||
_flags != config._flags ||
_textDirection != config.textDirection ||
_sortOrder != config._sortOrder ||
_sortKey != config._sortKey ||
_textSelection != config._textSelection ||
_scrollPosition != config._scrollPosition ||
_scrollExtentMax != config._scrollExtentMax ||
......@@ -1105,12 +1096,17 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
TextDirection get textDirection => _textDirection;
TextDirection _textDirection = _kEmptyConfig.textDirection;
/// The sort order for ordering the traversal of [SemanticsNode]s by the
/// platform's accessibility services (e.g. VoiceOver on iOS and TalkBack on
/// Android). This is used to determine the [nextNodeId] and [previousNodeId]
/// during a semantics update.
SemanticsSortOrder get sortOrder => _sortOrder;
SemanticsSortOrder _sortOrder;
/// Determines the position of this node among its siblings in the traversal
/// sort order.
///
/// This is used to describe the order in which the semantic node should be
/// traversed by the accessibility services on the platform (e.g. VoiceOver
/// on iOS and TalkBack on Android).
///
/// This is used to determine the [nextNodeId] and [previousNodeId] during a
/// semantics update.
SemanticsSortKey get sortKey => _sortKey;
SemanticsSortKey _sortKey;
/// The ID of the next node in the traversal order after this node.
///
......@@ -1219,7 +1215,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_hint = config.hint;
_flags = config._flags;
_textDirection = config.textDirection;
_sortOrder = config.sortOrder;
_sortKey = config.sortKey;
_actions = new Map<SemanticsAction, _SemanticsActionHandler>.from(config._actions);
_actionsAsBits = config._actionsAsBits;
_textSelection = config._textSelection;
......@@ -1422,7 +1418,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
properties.add(new EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null));
properties.add(new IntProperty('nextNodeId', _nextNodeId, defaultValue: null));
properties.add(new IntProperty('previousNodeId', _previousNodeId, defaultValue: null));
properties.add(new DiagnosticsProperty<SemanticsSortOrder>('sortOrder', sortOrder, defaultValue: null));
properties.add(new DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
if (_textSelection?.isValid == true)
properties.add(new MessageProperty('text selection', '[${_textSelection.start}, ${_textSelection.end}]'));
properties.add(new DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
......@@ -1489,18 +1485,21 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
}
}
/// This class defines the comparison that is used to sort [SemanticsNode]s
/// before sending them to the platform side.
/// The implementation of [Comparable] that implements the ordering of
/// [SemanticsNode]s in the accessibility traversal.
///
/// This is a helper class used to contain a [node], the effective
/// [order], the globally transformed starting corner [globalStartCorner],
/// and the containing node's [containerTextDirection] during the traversal of
/// the semantics node tree. A null value is allowed for [containerTextDirection],
/// because in that case we want to fall back to ordering by child insertion
/// order for nodes that are equal after sorting from top to bottom.
/// [SemanticsNode]s are sorted prior to sending them to the engine side.
///
/// This implementation considers a [node]'s [sortKey], it's parent's text
/// direction ([containerTextDirection]), and its geometric position relative to
/// its siblings ([globalStartCorner]).
///
/// A null value is allowed for [containerTextDirection], because in that case
/// we want to fall back to ordering by child insertion order for nodes that are
/// equal after sorting from top to bottom.
class _TraversalSortNode implements Comparable<_TraversalSortNode> {
_TraversalSortNode(this.node, this.order, this.containerTextDirection, Matrix4 transform)
: assert(node != null) {
_TraversalSortNode({@required this.node, this.containerTextDirection, this.sortKey, Matrix4 transform})
: assert(node != null),
// When containerTextDirection is null, this is set to topLeft, but the x
// coordinate is also ignored when doing the comparison in that case, so
// this isn't actually expressing a directionality opinion.
......@@ -1508,18 +1507,19 @@ class _TraversalSortNode implements Comparable<_TraversalSortNode> {
containerTextDirection == TextDirection.rtl ? node.rect.topRight : node.rect.topLeft,
transform,
);
}
/// The node that this sort node represents.
SemanticsNode node;
/// The node whose position this sort node determines.
final SemanticsNode node;
/// The effective text direction for this node is the directionality that
/// its container has.
TextDirection containerTextDirection;
final TextDirection containerTextDirection;
/// This is the effective sort order for this node, taking into account its
/// parents.
SemanticsSortOrder order;
/// Determines the position of this node among its siblings.
///
/// Sort keys take precedence over other attributes, such as
/// [globalStartCorner].
final SemanticsSortKey sortKey;
/// The starting corner for the rectangle on this semantics node in
/// global coordinates.
......@@ -1528,7 +1528,7 @@ class _TraversalSortNode implements Comparable<_TraversalSortNode> {
/// upper left corner. When the container has the directionality
/// [TextDirection.rtl], this is the upper right corner. When the container
/// has no directionality, this is set, but the x coordinate is ignored.
Offset globalStartCorner;
final Offset globalStartCorner;
static Offset _transformPoint(Offset point, Matrix4 matrix) {
final Vector3 result = matrix.transform3(new Vector3(point.dx, point.dy, 0.0));
......@@ -1563,10 +1563,10 @@ class _TraversalSortNode implements Comparable<_TraversalSortNode> {
@override
int compareTo(_TraversalSortNode other) {
if (order == null || other?.order == null) {
if (sortKey == null || other?.sortKey == null) {
return _compareGeometry(other);
}
final int comparison = order.compareTo(other.order);
final int comparison = sortKey.compareTo(other.sortKey);
if (comparison != 0) {
return comparison;
}
......@@ -1603,48 +1603,37 @@ class SemanticsOwner extends ChangeNotifier {
// accessibility services. If the nextNodeId or previousNodeId for a node
// changes, the node will be marked as dirty.
void _updateTraversalOrder() {
final List<_TraversalSortNode> nodesInSemanticsTraversalOrder = <_TraversalSortNode>[];
SemanticsSortOrder currentSortOrder = new SemanticsSortOrder(keys: <SemanticsSortKey>[]);
Matrix4 currentTransform = new Matrix4.identity();
TextDirection currentTextDirection = rootSemanticsNode.textDirection;
bool visitor(SemanticsNode node) {
final SemanticsSortOrder previousOrder = currentSortOrder;
final Matrix4 previousTransform = currentTransform.clone();
if (node.sortOrder != null) {
currentSortOrder = currentSortOrder.merge(node.sortOrder);
}
if (node.transform != null) {
currentTransform.multiply(node.transform);
}
final _TraversalSortNode traversalNode = new _TraversalSortNode(
node,
currentSortOrder,
currentTextDirection,
currentTransform,
);
// The text direction in force here is the parent's text direction.
nodesInSemanticsTraversalOrder.add(traversalNode);
if (node.hasChildren) {
final TextDirection previousTextDirection = currentTextDirection;
currentTextDirection = node.textDirection;
// Now visit the children with this node's text direction in force.
node.visitChildren(visitor);
currentTextDirection = previousTextDirection;
}
currentSortOrder = previousOrder;
currentTransform = previousTransform;
void updateRecursively(SemanticsNode parent, Matrix4 parentGlobalTransform) {
assert(parentGlobalTransform != null);
final List<_TraversalSortNode> children = <_TraversalSortNode>[];
parent.visitChildren((SemanticsNode child) {
final Matrix4 childGlobalTransform = child.transform != null
? parentGlobalTransform.multiplied(child.transform)
: parentGlobalTransform;
children.add(new _TraversalSortNode(
node: child,
containerTextDirection: parent.textDirection,
sortKey: child.sortKey,
transform: childGlobalTransform,
));
updateRecursively(child, childGlobalTransform);
return true;
}
rootSemanticsNode.visitChildren(visitor);
});
if (nodesInSemanticsTraversalOrder.isEmpty)
if (children.isEmpty) {
// We need at least one node for the following code to work.
return;
}
nodesInSemanticsTraversalOrder.sort();
_TraversalSortNode node = nodesInSemanticsTraversalOrder.removeLast();
children.sort();
_TraversalSortNode node = children.removeLast();
node.node._updateNextNodeId(-1);
while (nodesInSemanticsTraversalOrder.isNotEmpty) {
final _TraversalSortNode previousNode = nodesInSemanticsTraversalOrder.removeLast();
while (children.isNotEmpty) {
final _TraversalSortNode previousNode = children.removeLast();
node.node._updatePreviousNodeId(previousNode.node.id);
previousNode.node._updateNextNodeId(node.node.id);
node = previousNode;
......@@ -1652,6 +1641,9 @@ class SemanticsOwner extends ChangeNotifier {
node.node._updatePreviousNodeId(-1);
}
updateRecursively(rootSemanticsNode, new Matrix4.identity());
}
/// Update the semantics using [Window.updateSemantics].
void sendSemanticsUpdate() {
if (_dirtyNodes.isEmpty)
......@@ -2213,19 +2205,22 @@ class SemanticsConfiguration {
/// * [addAction] to add an action.
_SemanticsActionHandler getActionHandler(SemanticsAction action) => _actions[action];
/// The semantics traversal order.
///
/// This is used to sort this semantic node with all other semantic
/// nodes to determine the traversal order of accessible nodes.
/// Determines the position of this node among its siblings in the traversal
/// sort order.
///
/// See also:
/// This is used to describe the order in which the semantic node should be
/// traversed by the accessibility services on the platform (e.g. VoiceOver
/// on iOS and TalkBack on Android).
///
/// * [SemanticsSortOrder], which manages a list of sort keys.
SemanticsSortOrder get sortOrder => _sortOrder;
SemanticsSortOrder _sortOrder;
set sortOrder(SemanticsSortOrder value) {
/// Whether this sort key has an effect on the [SemanticsNode] sort order is
/// subject to how this configuration is used. For example, the [absorb]
/// method may decide to not use this key when it combines multiple
/// [SemanticsConfiguration] objects.
SemanticsSortKey get sortKey => _sortKey;
SemanticsSortKey _sortKey;
set sortKey(SemanticsSortKey value) {
assert(value != null);
_sortOrder = value;
_sortKey = value;
_hasBeenAnnotated = true;
}
......@@ -2558,7 +2553,7 @@ class SemanticsConfiguration {
_scrollExtentMin ??= other._scrollExtentMin;
textDirection ??= other.textDirection;
_sortOrder = _sortOrder?.merge(other._sortOrder);
_sortKey ??= other._sortKey;
_label = _concatStrings(
thisString: _label,
thisTextDirection: textDirection,
......@@ -2590,7 +2585,7 @@ class SemanticsConfiguration {
.._hasBeenAnnotated = _hasBeenAnnotated
.._isMergingSemanticsOfDescendants = _isMergingSemanticsOfDescendants
.._textDirection = _textDirection
.._sortOrder = _sortOrder
.._sortKey = _sortKey
.._label = _label
.._increasedValue = _increasedValue
.._value = _value
......@@ -2654,140 +2649,6 @@ String _concatStrings({
return '$thisString\n$nestedLabel';
}
/// Provides a way to specify the order in which semantic nodes are sorted.
///
/// [SemanticsSortOrder] objects contain a list of sort keys in the order in
/// which they are applied. They are attached to [Semantics] widgets in the
/// widget hierarchy, and are merged with the sort orders of their parent
/// [Semantics] widgets. If [SemanticsSortOrder.discardParentOrder] is set to
/// true, then they will instead ignore the sort order from the parents.
///
/// Keys at the same position in the sort order are compared with each other,
/// and keys which are of different types, or which have different
/// [SemanticSortKey.name] values compare as "equal" so that two different types
/// of keys can co-exist at the same level and not interfere with each other,
/// allowing for sorting into groups. Keys that evaluate as equal, or when
/// compared with Widgets that don't have [Semantics], fall back to the default
/// upper-start-to-lower-end geometric ordering if a text directionality
/// exists, and they sort from top to bottom followed by child insertion order
/// when no directionality is present.
///
/// Since widgets are globally sorted by their sort key, the order does not have
/// to conform to the widget hierarchy.
///
/// This class takes either `key` or `keys` at construction, but not both. The
/// `key` argument is just shorthand for specifying `<SemanticsSortKey>[key]`
/// for the `keys` argument.
///
/// ## Sample code
///
/// ```dart
/// class MyApp extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return new Column(
/// children: <Widget>[
/// new Semantics(
/// sortOrder: new SemanticsSortOrder(key: new OrdinalSortKey(2.0)),
/// child: const Text('Label One'),
/// ),
/// new Semantics(
/// sortOrder: new SemanticsSortOrder(key: new OrdinalSortKey(1.0)),
/// child: const Text('Label Two'),
/// ),
/// ],
/// );
/// }
/// }
/// ```
///
/// The above will create two [Text] widgets with "Label One" and "Label Two" as
/// their text, but, in accessibility mode, "Label Two" will be traversed first,
/// and "Label One" will be next. Without the sort keys, they would be traversed
/// top to bottom instead.
///
/// See also:
///
/// * [Semantics] for an object that annotates widgets with accessibility
/// semantics.
/// * [SemanticsSortKey] for the base class of the sort keys which
/// [SemanticsSortOrder] manages.
/// * [OrdinalSortKey] for a sort key that sorts using an ordinal.
class SemanticsSortOrder extends Diagnosticable implements Comparable<SemanticsSortOrder> {
/// Creates an object that describes the sort order for a [Semantics] widget.
///
/// Only one of `key` or `keys` may be specified, but at least one must
/// be specified. Specifying `key` is a shorthand for specifying
/// `keys: <SemanticsSortKey>[key]`.
///
/// If [discardParentOrder] is set to true, then the [SemanticsSortOrder.keys]
/// will _replace_ the list of keys from the parents when merged. Otherwise,
/// the child's keys are appended at the end of the parent's keys.
SemanticsSortOrder({
SemanticsSortKey key,
List<SemanticsSortKey> keys,
this.discardParentOrder = false,
}) : assert(key != null || keys != null, 'One of key or keys must be specified.'),
assert(key == null || keys == null, 'Only one of key or keys may be specified.'),
keys = key == null ? keys : <SemanticsSortKey>[key];
/// Whether or not this order is to replace the keys above it in the
/// semantics tree, or to be appended to them.
final bool discardParentOrder;
/// The keys that should be used to sort this node.
///
/// Typically only one key is provided, using the constructor's `key` argument.
final List<SemanticsSortKey> keys;
/// Merges two sort orders by concatenating their sort key lists. If
/// other.discardParentOrder is true, then other's sort key list replaces
/// that of the list in this object.
SemanticsSortOrder merge(SemanticsSortOrder other) {
if (other == null)
return this;
if (other.discardParentOrder) {
return new SemanticsSortOrder(
keys: new List<SemanticsSortKey>.from(other.keys),
discardParentOrder: discardParentOrder,
);
}
return new SemanticsSortOrder(
keys: new List<SemanticsSortKey>.from(keys)
..addAll(other.keys),
discardParentOrder: discardParentOrder,
);
}
@override
int compareTo(SemanticsSortOrder other) {
if (this == other) {
return 0;
}
for (int i = 0; i < keys.length && i < other.keys.length; ++i) {
final int comparison = keys[i].compareTo(other.keys[i]);
if (comparison != 0) {
return comparison;
}
}
// If there are more keys to compare, then assume that the shorter
// list comes before the longer list.
return keys.length.compareTo(other.keys.length);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new IterableProperty<SemanticsSortKey>('keys', keys, ifEmpty: null));
description.add(new FlagProperty(
'replace',
value: discardParentOrder,
defaultValue: false,
ifTrue: 'replace',
));
}
}
/// Base class for all sort keys for [Semantics] accessibility traversal order
/// sorting.
///
......
......@@ -4875,13 +4875,9 @@ class Semantics extends SingleChildRenderObjectWidget {
/// The [container] argument must not be null. To create a `const` instance
/// of [Semantics], use the [Semantics.fromProperties] constructor.
///
/// Only one of [sortKey] or [sortOrder] may be specified. Specifying [sortKey]
/// is just a shorthand for specifying `new SemanticsSortOrder(key: sortKey)`
/// for the [sortOrder].
///
/// See also:
///
/// * [SemanticsSortOrder] for a class that determines accessibility traversal
/// * [SemanticsSortKey] for a class that determines accessibility traversal
/// order.
Semantics({
Key key,
......@@ -4902,7 +4898,6 @@ class Semantics extends SingleChildRenderObjectWidget {
String decreasedValue,
String hint,
TextDirection textDirection,
SemanticsSortOrder sortOrder,
SemanticsSortKey sortKey,
VoidCallback onTap,
VoidCallback onLongPress,
......@@ -4940,7 +4935,7 @@ class Semantics extends SingleChildRenderObjectWidget {
decreasedValue: decreasedValue,
hint: hint,
textDirection: textDirection,
sortOrder: _effectiveSortOrder(sortKey, sortOrder),
sortKey: sortKey,
onTap: onTap,
onLongPress: onLongPress,
onScrollLeft: onScrollLeft,
......@@ -4972,11 +4967,6 @@ class Semantics extends SingleChildRenderObjectWidget {
assert(properties != null),
super(key: key, child: child);
static SemanticsSortOrder _effectiveSortOrder(SemanticsSortKey sortKey, SemanticsSortOrder sortOrder) {
assert(sortOrder == null || sortKey == null, 'Only one of sortOrder or sortKey may be specified.');
return sortOrder ?? (sortKey != null ? new SemanticsSortOrder(key: sortKey) : null);
}
/// Contains properties used by assistive technologies to make the application
/// more accessible.
final SemanticsProperties properties;
......@@ -5023,7 +5013,7 @@ class Semantics extends SingleChildRenderObjectWidget {
decreasedValue: properties.decreasedValue,
hint: properties.hint,
textDirection: _getTextDirection(context),
sortOrder: properties.sortOrder,
sortKey: properties.sortKey,
onTap: properties.onTap,
onLongPress: properties.onLongPress,
onScrollLeft: properties.onScrollLeft,
......@@ -5069,7 +5059,7 @@ class Semantics extends SingleChildRenderObjectWidget {
..decreasedValue = properties.decreasedValue
..hint = properties.hint
..textDirection = _getTextDirection(context)
..sortOrder = properties.sortOrder
..sortKey = properties.sortKey
..onTap = properties.onTap
..onLongPress = properties.onLongPress
..onScrollLeft = properties.onScrollLeft
......
......@@ -141,6 +141,8 @@ void main() {
);
expect(semantics, includesNodeWith(label: 'a label'));
semantics.dispose();
});
testWidgets('Inherited text direction rtl', (WidgetTester tester) async {
......
......@@ -521,7 +521,6 @@ void main() {
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'AC\nTab 1 of 3',
textDirection: TextDirection.ltr,
nextNodeId: -1,
previousNodeId: 3, // Should be 2
),
new TestSemantics(
......
......@@ -100,6 +100,7 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
nextNodeId: 3,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: null,
flags: <SemanticsFlag>[
......@@ -113,6 +114,8 @@ void main() {
),
new TestSemantics.rootChild(
id: 3,
previousNodeId: 1,
nextNodeId: 5,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: new Matrix4.translationValues(0.0, 56.0, 0.0),
flags: <SemanticsFlag>[
......@@ -126,6 +129,7 @@ void main() {
),
new TestSemantics.rootChild(
id: 5,
previousNodeId: 3,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: new Matrix4.translationValues(0.0, 112.0, 0.0),
flags: <SemanticsFlag>[
......@@ -139,6 +143,8 @@ void main() {
),
],
)));
semantics.dispose();
});
}
......@@ -622,5 +622,7 @@ void _tests() {
ignoreRect: true,
));
});
semantics.dispose();
});
}
......@@ -1363,6 +1363,7 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 3,
nextNodeId: 4,
actions: SemanticsAction.tap.index,
flags: SemanticsFlag.isSelected.index,
label: 'TAB #0\nTab 1 of 2',
......@@ -1371,6 +1372,7 @@ void main() {
),
new TestSemantics(
id: 4,
previousNodeId: 3,
actions: SemanticsAction.tap.index,
label: 'TAB #1\nTab 2 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
......@@ -1620,6 +1622,7 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 3,
nextNodeId: 4,
actions: SemanticsAction.tap.index,
flags: SemanticsFlag.isSelected.index,
label: 'Semantics override 0\nTab 1 of 2',
......@@ -1628,6 +1631,7 @@ void main() {
),
new TestSemantics(
id: 4,
previousNodeId: 3,
actions: SemanticsAction.tap.index,
label: 'Semantics override 1\nTab 2 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
......
......@@ -1675,6 +1675,8 @@ void main() {
);
expect(semantics, includesNodeWith(flags: <SemanticsFlag>[SemanticsFlag.isTextField]));
semantics.dispose();
});
testWidgets('Caret works when maxLines is null', (WidgetTester tester) async {
......@@ -1771,7 +1773,7 @@ void main() {
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
id: 1,
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[
SemanticsAction.tap,
......@@ -1789,7 +1791,7 @@ void main() {
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
id: 1,
textDirection: TextDirection.ltr,
value: 'Guten Tag',
actions: <SemanticsAction>[
......@@ -1808,7 +1810,7 @@ void main() {
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
id: 1,
textDirection: TextDirection.ltr,
value: 'Guten Tag',
textSelection: const TextSelection.collapsed(offset: 9),
......@@ -1832,7 +1834,7 @@ void main() {
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
id: 1,
textDirection: TextDirection.ltr,
textSelection: const TextSelection.collapsed(offset: 4),
value: 'Guten Tag',
......@@ -1858,7 +1860,7 @@ void main() {
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
id: 1,
textDirection: TextDirection.ltr,
textSelection: const TextSelection.collapsed(offset: 0),
value: 'Schönen Feierabend',
......@@ -1897,7 +1899,7 @@ void main() {
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
id: 1,
value: 'Hello',
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[
......@@ -1917,7 +1919,7 @@ void main() {
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
id: 1,
value: 'Hello',
textSelection: const TextSelection.collapsed(offset: 5),
textDirection: TextDirection.ltr,
......@@ -1941,7 +1943,7 @@ void main() {
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
id: 1,
value: 'Hello',
textSelection: const TextSelection(baseOffset: 5, extentOffset: 3),
textDirection: TextDirection.ltr,
......@@ -1985,7 +1987,7 @@ void main() {
await tester.tap(find.byKey(key));
await tester.pump();
const int inputFieldId = 2;
const int inputFieldId = 1;
expect(controller.selection, const TextSelection.collapsed(offset: 5, affinity: TextAffinity.upstream));
expect(semantics, hasSemantics(new TestSemantics.root(
......
......@@ -457,6 +457,8 @@ void _tests() {
action: SemanticsAction.decrease,
finalValue: '23',
);
semantics.dispose();
});
testWidgets('can increment and decrement minutes', (WidgetTester tester) async {
......@@ -501,6 +503,8 @@ void _tests() {
action: SemanticsAction.decrease,
finalValue: '58',
);
semantics.dispose();
});
}
......
......@@ -144,7 +144,7 @@ void main() {
});
test('OrdinalSortKey compares correctly', () {
final List<List<SemanticsSortKey>> tests = <List<SemanticsSortKey>>[
const List<List<SemanticsSortKey>> tests = const <List<SemanticsSortKey>>[
<SemanticsSortKey>[const OrdinalSortKey(0.0), const OrdinalSortKey(0.0)],
<SemanticsSortKey>[const OrdinalSortKey(0.0), const OrdinalSortKey(1.0)],
<SemanticsSortKey>[const OrdinalSortKey(1.0), const OrdinalSortKey(0.0)],
......@@ -163,31 +163,11 @@ void main() {
expect(results, orderedEquals(expectedResults));
});
test('SemanticsSortOrder sorts correctly', () {
final SemanticsSortOrder order1 = new SemanticsSortOrder(key: const CustomSortKey(0.0));
final SemanticsSortOrder order2 = new SemanticsSortOrder(key: const CustomSortKey(0.0));
// Equal single keys compare equal.
expect(order1.compareTo(order2), equals(0));
// Key lists that are longer compare as after the shorter ones.
order1.keys.add(const OrdinalSortKey(1.0));
expect(order1.compareTo(order2), equals(1));
// Equal multiple key lists compare equal.
order2.keys.add(const OrdinalSortKey(1.0));
expect(order1.compareTo(order2), equals(0));
// Different types compare equal.
order1.keys.add(const OrdinalSortKey(1.0));
order2.keys.add(const CustomSortKey(1.0));
expect(order1.compareTo(order2), equals(0));
// Unequal multiple-key lists sort the shorter list first.
order1.keys.add(const CustomSortKey(2.0));
expect(order1.compareTo(order2), equals(1));
});
test('SemanticsSortOrder sorts correctly when assigned names', () {
final SemanticsSortOrder order1g1 = new SemanticsSortOrder(key: const CustomSortKey(0.0, name: 'group 1'));
final SemanticsSortOrder order2g1 = new SemanticsSortOrder(key: const CustomSortKey(1.0, name: 'group 1'));
final SemanticsSortOrder order2g2 = new SemanticsSortOrder(key: const CustomSortKey(1.0, name: 'group 2'));
final SemanticsSortOrder order3g2 = new SemanticsSortOrder(key: const OrdinalSortKey(1.0, name: 'group 1'));
test('SemanticsSortKey sorts correctly when assigned names', () {
const SemanticsSortKey order1g1 = const CustomSortKey(0.0, name: 'group 1');
const SemanticsSortKey order2g1 = const CustomSortKey(1.0, name: 'group 1');
const SemanticsSortKey order2g2 = const CustomSortKey(1.0, name: 'group 2');
const SemanticsSortKey order3g2 = const OrdinalSortKey(1.0, name: 'group 1');
// Keys in the same group compare.
expect(order1g1.compareTo(order2g1), equals(-1));
// Keys with different names compare equal.
......@@ -196,26 +176,8 @@ void main() {
expect(order1g1.compareTo(order3g2), equals(0));
});
test('SemanticsSortOrder replaces correctly in merge', () {
final SemanticsSortOrder order1 = new SemanticsSortOrder(keys: <SemanticsSortKey>[const CustomSortKey(0.0), const OrdinalSortKey(0.0)]);
final SemanticsSortOrder order2 = new SemanticsSortOrder(keys: <SemanticsSortKey>[const CustomSortKey(0.0), const OrdinalSortKey(0.0)]);
final SemanticsSortOrder order3 = new SemanticsSortOrder(keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(1.0)], discardParentOrder: true);
// Equal single keys compare equal.
expect(order1.compareTo(order2), equals(0));
// Merged orders with one that replaces merge correctly.
final SemanticsSortOrder merged = order1.merge(order3);
expect(merged.keys.length, 2);
expect(merged.keys, orderedEquals(order3.keys));
expect(merged.compareTo(order2), 1);
// Merged orders with one that doesn't replace merge correctly.
final SemanticsSortOrder merged2 = order1.merge(order2);
expect(merged2.keys.length, 4);
expect(merged2.keys, orderedEquals(<SemanticsSortKey>[]..addAll(order1.keys)..addAll(order2.keys)));
expect(merged2.compareTo(order2), 1); // (merged2 is longer, so greater than).
});
test('OrdinalSortKey compares correctly', () {
final List<List<SemanticsSortKey>> tests = <List<SemanticsSortKey>>[
const List<List<SemanticsSortKey>> tests = const <List<SemanticsSortKey>>[
<SemanticsSortKey>[const OrdinalSortKey(0.0), const OrdinalSortKey(0.0)],
<SemanticsSortKey>[const OrdinalSortKey(0.0), const OrdinalSortKey(1.0)],
<SemanticsSortKey>[const OrdinalSortKey(1.0), const OrdinalSortKey(0.0)],
......@@ -234,31 +196,11 @@ void main() {
expect(results, orderedEquals(expectedResults));
});
test('SemanticsSortOrder sorts correctly', () {
final SemanticsSortOrder order1 = new SemanticsSortOrder(key: const CustomSortKey(0.0));
final SemanticsSortOrder order2 = new SemanticsSortOrder(key: const CustomSortKey(0.0));
// Equal single keys compare equal.
expect(order1.compareTo(order2), equals(0));
// Key lists that are longer compare as after the shorter ones.
order1.keys.add(const OrdinalSortKey(1.0));
expect(order1.compareTo(order2), equals(1));
// Equal multiple key lists compare equal.
order2.keys.add(const OrdinalSortKey(1.0));
expect(order1.compareTo(order2), equals(0));
// Different types compare equal.
order1.keys.add(const OrdinalSortKey(1.0));
order2.keys.add(const CustomSortKey(1.0));
expect(order1.compareTo(order2), equals(0));
// Unequal multiple-key lists sort the shorter list first.
order1.keys.add(const CustomSortKey(2.0));
expect(order1.compareTo(order2), equals(1));
});
test('SemanticsSortOrder sorts correctly when assigned names', () {
final SemanticsSortOrder order1g1 = new SemanticsSortOrder(key: const CustomSortKey(0.0, name: 'group 1'));
final SemanticsSortOrder order2g1 = new SemanticsSortOrder(key: const CustomSortKey(1.0, name: 'group 1'));
final SemanticsSortOrder order2g2 = new SemanticsSortOrder(key: const CustomSortKey(1.0, name: 'group 2'));
final SemanticsSortOrder order3g2 = new SemanticsSortOrder(key: const OrdinalSortKey(1.0, name: 'group 1'));
test('SemanticsSortKey sorts correctly when assigned names', () {
const SemanticsSortKey order1g1 = const CustomSortKey(0.0, name: 'group 1');
const SemanticsSortKey order2g1 = const CustomSortKey(1.0, name: 'group 1');
const SemanticsSortKey order2g2 = const CustomSortKey(1.0, name: 'group 2');
const SemanticsSortKey order3g2 = const OrdinalSortKey(1.0, name: 'group 1');
// Keys in the same group compare.
expect(order1g1.compareTo(order2g1), equals(-1));
// Keys with different names compare equal.
......@@ -267,24 +209,6 @@ void main() {
expect(order1g1.compareTo(order3g2), equals(0));
});
test('SemanticsSortOrder replaces correctly in merge', () {
final SemanticsSortOrder order1 = new SemanticsSortOrder(keys: <SemanticsSortKey>[const CustomSortKey(0.0), const OrdinalSortKey(0.0)]);
final SemanticsSortOrder order2 = new SemanticsSortOrder(keys: <SemanticsSortKey>[const CustomSortKey(0.0), const OrdinalSortKey(0.0)]);
final SemanticsSortOrder order3 = new SemanticsSortOrder(keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(1.0)], discardParentOrder: true);
// Equal single keys compare equal.
expect(order1.compareTo(order2), equals(0));
// Merged orders with one that replaces merge correctly.
final SemanticsSortOrder merged = order1.merge(order3);
expect(merged.keys.length, 2);
expect(merged.keys, orderedEquals(order3.keys));
expect(merged.compareTo(order2), 1);
// Merged orders with one that doesn't replace merge correctly.
final SemanticsSortOrder merged2 = order1.merge(order2);
expect(merged2.keys.length, 4);
expect(merged2.keys, orderedEquals(<SemanticsSortKey>[]..addAll(order1.keys)..addAll(order2.keys)));
expect(merged2.compareTo(order2), 1); // (merged2 is longer, so greater than).
});
test('toStringDeep respects childOrder parameter', () {
final SemanticsNode child1 = new SemanticsNode()
..rect = new Rect.fromLTRB(15.0, 0.0, 20.0, 5.0);
......@@ -445,7 +369,7 @@ void main() {
' textDirection: null\n'
' nextNodeId: null\n'
' previousNodeId: null\n'
' sortOrder: null\n'
' sortKey: null\n'
' scrollExtentMin: null\n'
' scrollPosition: null\n'
' scrollExtentMax: null\n'
......@@ -462,7 +386,7 @@ void main() {
..isButton = true
..label = 'Use all the properties'
..textDirection = TextDirection.rtl
..sortOrder = new SemanticsSortOrder(keys: <SemanticsSortKey>[const OrdinalSortKey(1.0)]);
..sortKey = const OrdinalSortKey(1.0);
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))
......@@ -479,8 +403,7 @@ void main() {
' flags: hasCheckedState, isSelected, isButton\n'
' label: "Use all the properties"\n'
' textDirection: rtl\n'
' sortOrder: SemanticsSortOrder#b555b(keys:\n'
' [OrdinalSortKey#19df5(order: 1.0)])\n'
' sortKey: OrdinalSortKey#19df5(order: 1.0)\n'
),
);
expect(
......
......@@ -150,16 +150,20 @@ void _defineTests() {
children: <TestSemantics>[
new TestSemantics(
id: 3,
nextNodeId: 4,
previousNodeId: 2,
label: 'background',
rect: new Rect.fromLTRB(1.0, 1.0, 2.0, 2.0),
),
new TestSemantics(
id: 2,
nextNodeId: 3,
label: 'Hello',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 600.0),
),
new TestSemantics(
id: 4,
previousNodeId: 3,
label: 'foreground',
rect: new Rect.fromLTRB(1.0, 1.0, 2.0, 2.0),
),
......@@ -356,15 +360,11 @@ void _defineTests() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
previousNodeId: -1,
nextNodeId: expectedId,
children: <TestSemantics>[
new TestSemantics.rootChild(
id: expectedId,
rect: TestSemantics.fullScreen,
actions: allActions.fold(0, (int previous, SemanticsAction action) => previous | action.index),
previousNodeId: 1,
nextNodeId: -1,
),
]
),
......@@ -420,20 +420,15 @@ void _defineTests() {
),
));
const int expectedId = 2;
final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
previousNodeId: -1,
nextNodeId: expectedId,
children: <TestSemantics>[
new TestSemantics.rootChild(
id: expectedId,
id: 2,
rect: TestSemantics.fullScreen,
flags: SemanticsFlag.values.values.toList(),
previousNodeId: 1,
nextNodeId: -1,
),
]
),
......
......@@ -289,6 +289,8 @@ void main() {
await tester.pump();
expect(semantics, includesNodeWith(flags: <SemanticsFlag>[SemanticsFlag.isTextField, SemanticsFlag.isFocused]));
semantics.dispose();
});
testWidgets('EditableText includes text as value in semantics', (WidgetTester tester) async {
......@@ -327,6 +329,8 @@ void main() {
flags: <SemanticsFlag>[SemanticsFlag.isTextField],
value: value2,
));
semantics.dispose();
});
testWidgets('changing selection with keyboard does not show handles', (WidgetTester tester) async {
......@@ -678,7 +682,7 @@ void main() {
await tester.pump();
final SemanticsOwner owner = tester.binding.pipelineOwner.semanticsOwner;
const int expectedNodeId = 3;
const int expectedNodeId = 2;
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
......
......@@ -39,6 +39,8 @@ void main() {
expect(callCount, 1);
tester.binding.pipelineOwner.semanticsOwner.performAction(detectorId, SemanticsAction.scrollDown);
expect(callCount, 2);
semantics.dispose();
});
testWidgets('Horizontal gesture detector has up/down actions', (WidgetTester tester) async {
......@@ -71,5 +73,7 @@ void main() {
expect(callCount, 1);
tester.binding.pipelineOwner.semanticsOwner.performAction(detectorId, SemanticsAction.scrollRight);
expect(callCount, 2);
semantics.dispose();
});
}
......@@ -142,6 +142,8 @@ void main() {
);
expect(semantics, includesNodeWith(label: 'a label'));
semantics.dispose();
});
testWidgets('Null icon with semantic label', (WidgetTester tester) async {
......@@ -160,6 +162,8 @@ void main() {
);
expect(semantics, includesNodeWith(label: 'a label'));
semantics.dispose();
});
testWidgets('Changing semantic label from null doesn\'t rebuild tree ', (WidgetTester tester) async {
......
......@@ -79,10 +79,12 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 2,
nextNodeId: 3,
label: 'Michael Goderbauer',
),
new TestSemantics(
id: 3,
previousNodeId: 2,
label: 'goderbauer@google.com',
),
],
......@@ -233,16 +235,20 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 6,
nextNodeId: 7,
flags: SemanticsFlag.isSelected.index,
label: 'node 1',
),
new TestSemantics(
id: 7,
previousNodeId: 6,
nextNodeId: 8,
flags: SemanticsFlag.isSelected.index,
label: 'node 2',
),
new TestSemantics(
id: 8,
previousNodeId: 7,
flags: SemanticsFlag.isSelected.index,
label: 'node 3',
),
......
......@@ -16,11 +16,6 @@ void main() {
debugResetSemanticsIdCounter();
});
tearDown(() {
semantics?.dispose();
semantics = null;
});
testWidgets('scrollable exposes the correct semantic actions', (WidgetTester tester) async {
semantics = new SemanticsTester(tester);
......@@ -47,6 +42,8 @@ void main() {
await flingDown(tester);
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown]));
semantics.dispose();
});
testWidgets('showOnScreen works in scrollable', (WidgetTester tester) async {
......@@ -83,6 +80,8 @@ void main() {
await tester.pump(const Duration(seconds: 5));
expect(scrollController.offset, 0.0);
semantics.dispose();
});
testWidgets('showOnScreen works with pinned app bar and sliver list', (WidgetTester tester) async {
......@@ -141,6 +140,8 @@ void main() {
await tester.pump();
await tester.pump(const Duration(seconds: 5));
expect(tester.getTopLeft(find.byWidget(containers[1])).dy, kExpandedAppBarHeight);
semantics.dispose();
});
testWidgets('showOnScreen works with pinned app bar and individual slivers', (WidgetTester tester) async {
......@@ -205,6 +206,8 @@ void main() {
await tester.pump();
await tester.pump(const Duration(seconds: 5));
expect(tester.getTopLeft(find.byWidget(children[1])).dy, kToolbarHeight);
semantics.dispose();
});
testWidgets('correct scrollProgress', (WidgetTester tester) async {
......@@ -249,6 +252,8 @@ void main() {
SemanticsAction.scrollDown,
],
));
semantics.dispose();
});
testWidgets('correct scrollProgress for unbound', (WidgetTester tester) async {
......@@ -296,6 +301,8 @@ void main() {
SemanticsAction.scrollDown,
],
));
semantics.dispose();
});
testWidgets('Semantics tree is populated mid-scroll', (WidgetTester tester) async {
......@@ -321,6 +328,8 @@ void main() {
expect(semantics, includesNodeWith(label: 'Item 1'));
expect(semantics, includesNodeWith(label: 'Item 2'));
expect(semantics, includesNodeWith(label: 'Item 3'));
semantics.dispose();
});
testWidgets('Can toggle semantics on, off, on without crash', (WidgetTester tester) async {
......@@ -379,6 +388,8 @@ void main() {
await tester.pumpAndSettle();
expect(tester.binding.pipelineOwner.semanticsOwner, isNotNull);
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true, ignoreRect: true, ignoreTransform: true));
semantics.dispose();
});
}
......
......@@ -122,12 +122,14 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 2,
nextNodeId: 3,
label: 'child1',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlag.isSelected.index,
),
new TestSemantics(
id: 3,
previousNodeId: 2,
label: 'child2',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlag.isSelected.index,
......@@ -218,12 +220,14 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 4,
nextNodeId: 3,
label: 'child1',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlag.isSelected.index,
),
new TestSemantics(
id: 3,
previousNodeId: 4,
label: 'child2',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlag.isSelected.index,
......
......@@ -58,12 +58,14 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 2,
nextNodeId: 3,
label: 'child1',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlag.isSelected.index,
),
new TestSemantics(
id: 3,
previousNodeId: 2,
label: 'child2',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlag.isSelected.index,
......@@ -154,12 +156,14 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 4,
nextNodeId: 3,
label: 'child1',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlag.isSelected.index,
),
new TestSemantics(
id: 3,
previousNodeId: 4,
label: 'child2',
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
flags: SemanticsFlag.isSelected.index,
......
......@@ -54,21 +54,25 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
nextNodeId: 2,
label: 'L1',
rect: TestSemantics.fullScreen,
),
new TestSemantics.rootChild(
id: 2,
previousNodeId: 1,
label: 'L2',
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 3,
nextNodeId: 4,
flags: SemanticsFlag.hasCheckedState.index | SemanticsFlag.isChecked.index,
rect: TestSemantics.fullScreen,
),
new TestSemantics(
id: 4,
previousNodeId: 3,
flags: SemanticsFlag.hasCheckedState.index,
rect: TestSemantics.fullScreen,
),
......@@ -114,11 +118,13 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
nextNodeId: 2,
label: 'L1',
rect: TestSemantics.fullScreen,
),
new TestSemantics.rootChild(
id: 2,
previousNodeId: 1,
label: 'L2',
flags: SemanticsFlag.hasCheckedState.index | SemanticsFlag.isChecked.index,
rect: TestSemantics.fullScreen,
......
......@@ -37,10 +37,12 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
nextNodeId: 2,
rect: TestSemantics.fullScreen,
),
new TestSemantics.rootChild(
id: 2,
previousNodeId: 1,
label: 'label',
rect: TestSemantics.fullScreen,
),
......
......@@ -56,6 +56,7 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
nextNodeId: 4,
flags: SemanticsFlag.hasCheckedState.index | SemanticsFlag.isChecked.index,
label: label,
rect: TestSemantics.fullScreen,
......@@ -63,6 +64,7 @@ void main() {
// IDs 2 and 3 are used up by the nodes that get merged in
new TestSemantics.rootChild(
id: 4,
previousNodeId: 1,
flags: SemanticsFlag.hasCheckedState.index | SemanticsFlag.isChecked.index,
label: label,
rect: TestSemantics.fullScreen,
......@@ -112,6 +114,7 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
nextNodeId: 4,
flags: SemanticsFlag.hasCheckedState.index | SemanticsFlag.isChecked.index,
label: label,
rect: TestSemantics.fullScreen,
......@@ -119,6 +122,7 @@ void main() {
// IDs 2 and 3 are used up by the nodes that get merged in
new TestSemantics.rootChild(
id: 4,
previousNodeId: 1,
flags: SemanticsFlag.hasCheckedState.index | SemanticsFlag.isChecked.index,
label: label,
rect: TestSemantics.fullScreen,
......
......@@ -45,12 +45,10 @@ void main() {
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
id: 1,
label: '1',
rect: new Rect.fromLTRB(0.0, 0.0, 75.0, 14.0),
),
new TestSemantics(
id: 2,
label: '2',
rect: new Rect.fromLTRB(0.0, 0.0, 25.0, 14.0), // clipped form original 75.0 to 25.0
),
......@@ -58,6 +56,7 @@ void main() {
],
),
ignoreTransform: true,
ignoreId: true,
));
semantics.dispose();
......@@ -105,18 +104,17 @@ void main() {
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
id: 3,
label: '1',
rect: new Rect.fromLTRB(0.0, 0.0, 75.0, 14.0),
),
new TestSemantics(
id: 4,
label: '2\n3',
rect: new Rect.fromLTRB(0.0, 0.0, 25.0, 14.0), // clipped form original 75.0 to 25.0
),
],
),
ignoreTransform: true,
ignoreId: true,
));
semantics.dispose();
......
......@@ -41,8 +41,16 @@ void main() {
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(id: 1, label: 'test1'),
new TestSemantics.rootChild(id: 2, label: 'test2'),
new TestSemantics.rootChild(
id: 1,
label: 'test1',
nextNodeId: 2,
),
new TestSemantics.rootChild(
id: 2,
label: 'test2',
previousNodeId: 1,
),
],
),
ignoreRect: true,
......@@ -105,8 +113,8 @@ void main() {
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(id: 6, label: 'test1'),
new TestSemantics.rootChild(id: 7, label: 'test2'),
new TestSemantics.rootChild(id: 6, label: 'test1', nextNodeId: 7),
new TestSemantics.rootChild(id: 7, label: 'test2', previousNodeId: 6),
],
),
ignoreRect: true,
......
......@@ -160,6 +160,7 @@ void main() {
);
expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.rtl));
semantics.dispose();
});
testWidgets('Semantics and Directionality - LTR', (WidgetTester tester) async {
......@@ -176,6 +177,7 @@ void main() {
);
expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.ltr));
semantics.dispose();
});
testWidgets('Semantics and Directionality - cannot override RTL with LTR', (WidgetTester tester) async {
......@@ -202,6 +204,7 @@ void main() {
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics and Directionality - cannot override LTR with RTL', (WidgetTester tester) async {
......@@ -228,6 +231,7 @@ void main() {
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics label and hint', (WidgetTester tester) async {
......@@ -257,6 +261,7 @@ void main() {
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics hints can merge', (WidgetTester tester) async {
......@@ -292,6 +297,7 @@ void main() {
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics values do not merge', (WidgetTester tester) async {
......@@ -342,6 +348,7 @@ void main() {
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics value and hint can merge', (WidgetTester tester) async {
......@@ -377,6 +384,7 @@ void main() {
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics widget supports all actions', (WidgetTester tester) async {
......@@ -409,15 +417,13 @@ void main() {
final Set<SemanticsAction> allActions = SemanticsAction.values.values.toSet()
..remove(SemanticsAction.showOnScreen); // showOnScreen is non user-exposed.
const int expectedId = 2;
const int expectedId = 1;
final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: expectedId,
rect: TestSemantics.fullScreen,
actions: allActions.fold(0, (int previous, SemanticsAction action) => previous | action.index),
previousNodeId: -1,
nextNodeId: -1,
),
],
);
......@@ -472,8 +478,6 @@ void main() {
new TestSemantics.rootChild(
rect: TestSemantics.fullScreen,
flags: SemanticsFlag.values.values.toList(),
previousNodeId: -1,
nextNodeId: -1,
),
],
);
......@@ -485,7 +489,7 @@ void main() {
testWidgets('Actions can be replaced without triggering semantics update', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;
tester.binding.pipelineOwner.ensureSemantics(
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
}
......@@ -500,7 +504,7 @@ void main() {
),
);
const int expectedId = 2;
const int expectedId = 1;
final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
......@@ -575,6 +579,7 @@ void main() {
expect(semantics, hasSemantics(expectedSemantics));
expect(semanticsUpdateCount, 1);
handle.dispose();
semantics.dispose();
});
......@@ -613,7 +618,7 @@ void main() {
testWidgets('Semantics widgets built in a widget tree are sorted properly', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;
tester.binding.pipelineOwner.ensureSemantics(
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
}
......@@ -646,53 +651,46 @@ void main() {
);
expect(semanticsUpdateCount, 1);
expect(semantics, hasSemantics(
new TestSemantics(
id: 0,
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
id: 2,
nextNodeId: 5,
previousNodeId: -1,
id: 1,
children: <TestSemantics>[
new TestSemantics(
id: 3,
id: 2,
label: r'Label 1',
textDirection: TextDirection.ltr,
nextNodeId: -1,
previousNodeId: 4,
previousNodeId: 3,
),
new TestSemantics(
id: 4,
id: 3,
label: r'Label 2',
textDirection: TextDirection.ltr,
nextNodeId: 3,
previousNodeId: 6,
nextNodeId: 2,
previousNodeId: 4,
),
new TestSemantics(
id: 5,
nextNodeId: 8,
previousNodeId: 2,
id: 4,
nextNodeId: 3,
children: <TestSemantics>[
new TestSemantics(
id: 6,
id: 5,
label: r'Label 3',
textDirection: TextDirection.ltr,
nextNodeId: 4,
previousNodeId: 7,
previousNodeId: 6,
),
new TestSemantics(
id: 7,
id: 6,
label: r'Label 4',
textDirection: TextDirection.ltr,
nextNodeId: 6,
previousNodeId: 8,
nextNodeId: 5,
previousNodeId: 7,
),
new TestSemantics(
id: 8,
id: 7,
label: r'Label 5',
textDirection: TextDirection.ltr,
nextNodeId: 7,
previousNodeId: 5,
nextNodeId: 6,
),
],
),
......@@ -701,13 +699,15 @@ void main() {
],
), ignoreTransform: true, ignoreRect: true),
);
handle.dispose();
semantics.dispose();
});
testWidgets('Semantics widgets built with explicit sort orders are sorted properly', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;
tester.binding.pipelineOwner.ensureSemantics(
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
}
......@@ -715,192 +715,58 @@ void main() {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Column(
child: new Row(
children: <Widget>[
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(3.0), const OrdinalSortKey(5.0)],
),
sortKey: const CustomSortKey(3.0),
child: const Text('Label 1'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(2.0), const OrdinalSortKey(4.0)],
),
sortKey: const CustomSortKey(1.0),
child: const Text('Label 2'),
),
new Row(
children: <Widget>[
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(3.0)],
),
sortKey: const CustomSortKey(2.0),
child: const Text('Label 3'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(2.0)],
),
child: const Text('Label 4'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(1.0)],
),
child: const Text('Label 5'),
),
],
),
],
),
),
);
expect(semanticsUpdateCount, 1);
expect(semantics, hasSemantics(
new TestSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
id: 1,
label: r'Label 1',
textDirection: TextDirection.ltr,
nextNodeId: -1,
previousNodeId: 3,
),
new TestSemantics(
id: 2,
label: r'Label 2',
textDirection: TextDirection.ltr,
nextNodeId: 2,
previousNodeId: 4,
),
new TestSemantics(
label: r'Label 3',
textDirection: TextDirection.ltr,
nextNodeId: 3,
previousNodeId: 5,
),
new TestSemantics(
label: r'Label 4',
textDirection: TextDirection.ltr,
nextNodeId: 4,
previousNodeId: 6,
),
new TestSemantics(
label: r'Label 5',
textDirection: TextDirection.ltr,
nextNodeId: 5,
previousNodeId: -1,
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics widgets built with some discarded sort orders are sorted properly', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;
tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
}
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Semantics(
sortKey: const OrdinalSortKey(0.0),
explicitChildNodes: true,
child: new Column(
children: <Widget>[
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(3.0), const OrdinalSortKey(5.0)],
discardParentOrder: true, // Replace this one.
),
child: const Text('Label 1'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(2.0), const OrdinalSortKey(4.0)],
),
child: const Text('Label 2'),
),
new Row(
children: <Widget>[
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(3.0)],
discardParentOrder: true, // Replace this one.
),
child: const Text('Label 3'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(2.0)],
),
child: const Text('Label 4'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(1.0)],
),
child: const Text('Label 5'),
),
],
),
],
),
),
),
);
expect(semanticsUpdateCount, 1);
expect(semantics, hasSemantics(
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
nextNodeId: 5,
previousNodeId: -1,
children: <TestSemantics>[
new TestSemantics(
label: r'Label 1',
textDirection: TextDirection.ltr,
nextNodeId: 7,
previousNodeId: 5,
),
new TestSemantics(
label: r'Label 2',
textDirection: TextDirection.ltr,
nextNodeId: -1,
previousNodeId: 6,
),
new TestSemantics(
id: 3,
label: r'Label 3',
textDirection: TextDirection.ltr,
nextNodeId: 3,
nextNodeId: 1,
previousNodeId: 2,
),
new TestSemantics(
label: r'Label 4',
textDirection: TextDirection.ltr,
nextNodeId: 4,
previousNodeId: 7,
),
new TestSemantics(
label: r'Label 5',
textDirection: TextDirection.ltr,
nextNodeId: 6,
previousNodeId: 3,
),
],
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true),
);
), ignoreTransform: true, ignoreRect: true));
handle.dispose();
semantics.dispose();
});
testWidgets('Semantics widgets without sort orders are sorted properly', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;
tester.binding.pipelineOwner.ensureSemantics(
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
}
......@@ -930,38 +796,39 @@ void main() {
new TestSemantics(
label: r'Label 1',
textDirection: TextDirection.ltr,
previousNodeId: -1,
),
new TestSemantics(
label: r'Label 2',
textDirection: TextDirection.ltr,
previousNodeId: 2,
previousNodeId: 1,
),
new TestSemantics(
label: r'Label 3',
textDirection: TextDirection.ltr,
previousNodeId: 3,
previousNodeId: 2,
),
new TestSemantics(
label: r'Label 4',
textDirection: TextDirection.ltr,
previousNodeId: 4,
previousNodeId: 3,
),
new TestSemantics(
label: r'Label 5',
textDirection: TextDirection.ltr,
previousNodeId: 5,
previousNodeId: 4,
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true),
);
handle.dispose();
semantics.dispose();
});
testWidgets('Semantics widgets that are transformed are sorted properly', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;
tester.binding.pipelineOwner.ensureSemantics(
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
}
......@@ -994,31 +861,32 @@ void main() {
new TestSemantics(
label: r'Label 1',
textDirection: TextDirection.ltr,
previousNodeId: 6,
previousNodeId: 5,
),
new TestSemantics(
label: r'Label 2',
textDirection: TextDirection.ltr,
previousNodeId: 2,
previousNodeId: 1,
),
new TestSemantics(
label: r'Label 3',
textDirection: TextDirection.ltr,
previousNodeId: -1,
),
new TestSemantics(
label: r'Label 4',
textDirection: TextDirection.ltr,
previousNodeId: 4,
previousNodeId: 3,
),
new TestSemantics(
label: r'Label 5',
textDirection: TextDirection.ltr,
previousNodeId: 5,
previousNodeId: 4,
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true),
);
handle.dispose();
semantics.dispose();
});
......@@ -1027,7 +895,7 @@ void main() {
(WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;
tester.binding.pipelineOwner.ensureSemantics(listener: () {
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(listener: () {
semanticsUpdateCount += 1;
});
await tester.pumpWidget(
......@@ -1097,27 +965,26 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
previousNodeId: -1,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
previousNodeId: 6,
previousNodeId: 5,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
previousNodeId: 7,
previousNodeId: 6,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
previousNodeId: 4,
previousNodeId: 3,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
previousNodeId: 5,
previousNodeId: 4,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
previousNodeId: 2,
previousNodeId: 1,
),
],
),
......@@ -1125,6 +992,8 @@ void main() {
ignoreRect: true,
ignoreId: true),
);
handle.dispose();
semantics.dispose();
});
}
......
......@@ -43,8 +43,8 @@ class TestSemantics {
this.decreasedValue: '',
this.hint: '',
this.textDirection,
this.nextNodeId,
this.previousNodeId,
this.nextNodeId: -1,
this.previousNodeId: -1,
this.rect,
this.transform,
this.textSelection,
......@@ -58,6 +58,8 @@ class TestSemantics {
assert(decreasedValue != null),
assert(hint != null),
assert(children != null),
assert(nextNodeId != null),
assert(previousNodeId != null),
tags = tags?.toSet() ?? new Set<SemanticsTag>();
/// Creates an object with some test semantics data, with the [id] and [rect]
......@@ -71,8 +73,6 @@ class TestSemantics {
this.decreasedValue: '',
this.hint: '',
this.textDirection,
this.previousNodeId,
this.nextNodeId,
this.transform,
this.textSelection,
this.children: const <TestSemantics>[],
......@@ -87,6 +87,8 @@ class TestSemantics {
assert(hint != null),
rect = TestSemantics.rootRect,
assert(children != null),
nextNodeId = null,
previousNodeId = null,
tags = tags?.toSet() ?? new Set<SemanticsTag>();
/// Creates an object with some test semantics data, with the [id] and [rect]
......@@ -108,8 +110,8 @@ class TestSemantics {
this.increasedValue: '',
this.decreasedValue: '',
this.textDirection,
this.nextNodeId,
this.previousNodeId,
this.nextNodeId: -1,
this.previousNodeId: -1,
this.rect,
Matrix4 transform,
this.textSelection,
......@@ -124,6 +126,8 @@ class TestSemantics {
assert(hint != null),
transform = _applyRootChildScale(transform),
assert(children != null),
assert(nextNodeId != null),
assert(previousNodeId != null),
tags = tags?.toSet() ?? new Set<SemanticsTag>();
/// The unique identifier for this node.
......@@ -265,9 +269,9 @@ class TestSemantics {
return fail('expected node id $id to have hint "$hint" but found hint "${nodeData.hint}".');
if (textDirection != null && textDirection != nodeData.textDirection)
return fail('expected node id $id to have textDirection "$textDirection" but found "${nodeData.textDirection}".');
if (!ignoreId && nextNodeId != null && nextNodeId != nodeData.nextNodeId)
if (!ignoreId && nextNodeId != nodeData.nextNodeId)
return fail('expected node id $id to have nextNodeId "$nextNodeId" but found "${nodeData.nextNodeId}".');
if (!ignoreId && previousNodeId != null && previousNodeId != nodeData.previousNodeId)
if (!ignoreId && previousNodeId != nodeData.previousNodeId)
return fail('expected node id $id to have previousNodeId "$previousNodeId" but found "${nodeData.previousNodeId}".');
if ((nodeData.label != '' || nodeData.value != '' || nodeData.hint != '' || node.increasedValue != '' || node.decreasedValue != '') && nodeData.textDirection == null)
return fail('expected node id $id, which has a label, value, or hint, to have a textDirection, but it did not.');
......@@ -350,6 +354,14 @@ class SemanticsTester {
/// tester.
SemanticsTester(this.tester) {
_semanticsHandle = tester.binding.pipelineOwner.ensureSemantics();
// This _extra_ clean-up is needed for the case when a test fails and
// therefore fails to call dispose() explicitly. The test is still required
// to call dispose() explicitly, because the semanticsOwner check is
// performed irrespective of whether the owner was created via
// SemanticsTester or directly. When the test succeeds, this tear-down
// becomes a no-op.
addTearDown(dispose);
}
/// The widget tester that this object is testing the semantics of.
......@@ -358,10 +370,12 @@ class SemanticsTester {
/// Release resources held by this semantics tester.
///
/// Call this function at the end of any test that uses a semantics tester.
/// Call this function at the end of any test that uses a semantics tester. It
/// is OK to call this function multiple times. If the resources have already
/// been released, the subsequent calls have no effect.
@mustCallSuper
void dispose() {
_semanticsHandle.dispose();
_semanticsHandle?.dispose();
_semanticsHandle = null;
}
......@@ -493,6 +507,10 @@ class SemanticsTester {
return '<SemanticsFlag>[${list.join(', ')}]';
}
static String _tagsToSemanticsTagExpression(Set<SemanticsTag> tags) {
return '<SemanticsTag>[${tags.map((SemanticsTag tag) => 'const SemanticsTag(\'${tag.name}\')').join(', ')}]';
}
static String _actionsToSemanticsActionExpression(dynamic actions) {
Iterable<SemanticsAction> list;
if (actions is int) {
......@@ -510,32 +528,37 @@ class SemanticsTester {
final String indent = ' ' * indentAmount;
final StringBuffer buf = new StringBuffer();
final SemanticsData nodeData = node.getSemanticsData();
buf.writeln('new TestSemantics(');
final bool isRoot = node.id == 0;
buf.writeln('new TestSemantics${isRoot ? '.root': ''}(');
if (!isRoot)
buf.writeln(' id: ${node.id},');
if (nodeData.tags != null)
buf.writeln(' tags: ${_tagsToSemanticsTagExpression(nodeData.tags)},');
if (nodeData.flags != 0)
buf.writeln(' flags: ${_flagsToSemanticsFlagExpression(nodeData.flags)},');
if (nodeData.actions != 0)
buf.writeln(' actions: ${_actionsToSemanticsActionExpression(nodeData.actions)},');
if (node.label != null && node.label.isNotEmpty) {
final String escapedLabel = node.label.replaceAll('\n', r'\n');
if (escapedLabel == node.label) {
if (escapedLabel != node.label) {
buf.writeln(' label: r\'$escapedLabel\',');
} else {
buf.writeln(' label: \'$escapedLabel\',');
}
}
if (node.value != null && node.value.isNotEmpty)
buf.writeln(' value: r\'${node.value}\',');
buf.writeln(' value: \'${node.value}\',');
if (node.increasedValue != null && node.increasedValue.isNotEmpty)
buf.writeln(' increasedValue: r\'${node.increasedValue}\',');
buf.writeln(' increasedValue: \'${node.increasedValue}\',');
if (node.decreasedValue != null && node.decreasedValue.isNotEmpty)
buf.writeln(' decreasedValue: r\'${node.decreasedValue}\',');
buf.writeln(' decreasedValue: \'${node.decreasedValue}\',');
if (node.hint != null && node.hint.isNotEmpty)
buf.writeln(' hint: r\'${node.hint}\',');
buf.writeln(' hint: \'${node.hint}\',');
if (node.textDirection != null)
buf.writeln(' textDirection: ${node.textDirection},');
if (node.nextNodeId != null)
if (node.nextNodeId != null && node.nextNodeId != -1)
buf.writeln(' nextNodeId: ${node.nextNodeId},');
if (node.previousNodeId != null)
if (node.previousNodeId != null && node.previousNodeId != -1)
buf.writeln(' previousNodeId: ${node.previousNodeId},');
if (node.hasChildren) {
......
......@@ -102,32 +102,32 @@ void _tests() {
// generateTestSemanticsExpressionForCurrentSemanticsTree. Otherwise,
// the test 'generates code', defined above, will fail.
// vvvvvvvvvvvv
new TestSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics(
nextNodeId: 4,
previousNodeId: -1,
id: 1,
children: <TestSemantics>[
new TestSemantics(
nextNodeId: 2,
previousNodeId: 1,
id: 4,
children: <TestSemantics>[
new TestSemantics(
label: r'Plain text',
id: 2,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
label: 'Plain text',
textDirection: TextDirection.ltr,
nextNodeId: 3,
previousNodeId: 4,
),
new TestSemantics(
id: 3,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
flags: <SemanticsFlag>[SemanticsFlag.hasCheckedState, SemanticsFlag.isChecked, SemanticsFlag.isSelected],
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.decrease],
label: r'‪Interactive text‬',
value: r'test-value',
increasedValue: r'test-increasedValue',
decreasedValue: r'test-decreasedValue',
hint: r'test-hint',
label: '‪Interactive text‬',
value: 'test-value',
increasedValue: 'test-increasedValue',
decreasedValue: 'test-decreasedValue',
hint: 'test-hint',
textDirection: TextDirection.rtl,
nextNodeId: -1,
previousNodeId: 2,
),
],
......
......@@ -55,24 +55,31 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 5,
actions: SemanticsAction.scrollUp.index,
actions: <SemanticsAction>[SemanticsAction.scrollUp],
children: <TestSemantics>[
new TestSemantics(
id: 2,
label: 'Item 0',
label: r'Item 0',
textDirection: TextDirection.ltr,
nextNodeId: 3,
previousNodeId: 4,
),
new TestSemantics(
id: 3,
label: 'Item 1',
label: r'Item 1',
textDirection: TextDirection.ltr,
previousNodeId: 2,
),
new TestSemantics(
id: 4,
label: 'Semantics Test with Slivers',
label: r'Semantics Test with Slivers',
textDirection: TextDirection.ltr,
nextNodeId: 2,
),
],
),
],
)
),
],
),
ignoreRect: true,
......@@ -93,29 +100,38 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 5,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
nextNodeId: 4,
children: <TestSemantics>[
new TestSemantics(
id: 2,
label: 'Item 0',
label: r'Item 0',
textDirection: TextDirection.ltr,
nextNodeId: 3,
),
new TestSemantics(
id: 3,
label: 'Item 1',
label: r'Item 1',
textDirection: TextDirection.ltr,
nextNodeId: 6,
previousNodeId: 2,
),
new TestSemantics(
id: 6,
label: 'Item 2',
label: r'Item 2',
textDirection: TextDirection.ltr,
previousNodeId: 3,
),
],
),
new TestSemantics(
id: 4,
label: 'Semantics Test with Slivers',
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
label: r'Semantics Test with Slivers',
textDirection: TextDirection.ltr,
previousNodeId: 5,
),
],
)
),
],
),
ignoreRect: true,
......@@ -136,28 +152,38 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 5,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
children: <TestSemantics>[
new TestSemantics(
id: 2,
label: 'Item 0',
label: r'Item 0',
textDirection: TextDirection.ltr,
nextNodeId: 3,
previousNodeId: 4,
),
new TestSemantics(
id: 3,
label: 'Item 1',
label: r'Item 1',
textDirection: TextDirection.ltr,
nextNodeId: 6,
previousNodeId: 2,
),
new TestSemantics(
id: 6,
label: 'Item 2',
label: r'Item 2',
textDirection: TextDirection.ltr,
previousNodeId: 3,
),
new TestSemantics(
id: 4,
label: 'Semantics Test with Slivers',
label: r'Semantics Test with Slivers',
textDirection: TextDirection.ltr,
nextNodeId: 2,
),
],
),
],
)
),
],
),
ignoreRect: true,
......@@ -207,22 +233,24 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 10,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown],
children: <TestSemantics>[
new TestSemantics(
id: 8,
label: 'Item 2',
textDirection: TextDirection.ltr,
previousNodeId: 9,
),
new TestSemantics(
id: 9,
label: 'Item 1',
textDirection: TextDirection.ltr,
nextNodeId: 8,
),
],
),
],
)
),
],
),
ignoreRect: true,
......@@ -266,31 +294,39 @@ void main() {
id: 12,
label: 'Item 4',
textDirection: TextDirection.ltr,
previousNodeId: 13,
),
new TestSemantics(
id: 13,
label: 'Item 3',
textDirection: TextDirection.ltr,
nextNodeId: 12,
previousNodeId: 14,
),
new TestSemantics(
id: 14,
label: 'Item 2',
textDirection: TextDirection.ltr,
nextNodeId: 13,
previousNodeId: 15,
),
new TestSemantics(
id: 15,
label: 'Item 1',
textDirection: TextDirection.ltr,
nextNodeId: 14,
previousNodeId: 16,
),
new TestSemantics(
id: 16,
label: 'Item 0',
textDirection: TextDirection.ltr,
nextNodeId: 15,
),
],
),
],
)
),
],
),
ignoreRect: true,
......@@ -343,12 +379,14 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 23,
nextNodeId: 22,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
// Item 0 is missing because its covered by the app bar.
new TestSemantics(
id: 19,
nextNodeId: 20,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
// Item 1 starts 20.0dp below edge, so there would be room for Item 0.
transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
......@@ -356,6 +394,8 @@ void main() {
),
new TestSemantics(
id: 20,
nextNodeId: 21,
previousNodeId: 19,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
label: 'Item 2',
......@@ -365,6 +405,7 @@ void main() {
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
label: 'Item 3',
previousNodeId: 20,
),
],
),
......@@ -373,6 +414,7 @@ void main() {
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
label: 'AppBar',
previousNodeId: 23,
),
],
)
......@@ -426,23 +468,28 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 29,
nextNodeId: 28,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 25,
previousNodeId: 26,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
label: 'Item 3',
),
new TestSemantics(
id: 26,
nextNodeId: 25,
previousNodeId: 27,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
label: 'Item 2',
),
new TestSemantics(
id: 27,
nextNodeId: 26,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
// Item 1 starts 20.0dp below edge, so there would be room for Item 0.
transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
......@@ -453,6 +500,7 @@ void main() {
),
new TestSemantics(
id: 28,
previousNodeId: 29,
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
label: 'AppBar'
......@@ -511,12 +559,14 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 35,
nextNodeId: 34,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
// Item 0 is missing because its covered by the app bar.
new TestSemantics(
id: 31,
previousNodeId: 32,
// Item 1 ends at 580dp, so there would be 20dp space for Item 0.
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 380.0, 0.0)),
......@@ -524,12 +574,15 @@ void main() {
),
new TestSemantics(
id: 32,
nextNodeId: 31,
previousNodeId: 33,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 180.0, 0.0)),
label: 'Item 2',
),
new TestSemantics(
id: 33,
nextNodeId: 32,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, -20.0, 0.0)),
label: 'Item 3',
......@@ -538,6 +591,7 @@ void main() {
),
new TestSemantics(
id: 34,
previousNodeId: 35,
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
......@@ -596,23 +650,28 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 41,
nextNodeId: 40,
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 37,
nextNodeId: 38,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, -20.0, 0.0)),
label: 'Item 3',
),
new TestSemantics(
id: 38,
nextNodeId: 39,
previousNodeId: 37,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
transform: new Matrix4.translation(new Vector3(0.0, 180.0, 0.0)),
label: 'Item 2',
),
new TestSemantics(
id: 39,
previousNodeId: 38,
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
// Item 1 ends at 580dp, so there would be 20dp space for Item 0.
transform: new Matrix4.translation(new Vector3(0.0, 380.0, 0.0)),
......@@ -623,6 +682,7 @@ void main() {
),
new TestSemantics(
id: 40,
previousNodeId: 41,
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
......
......@@ -471,6 +471,19 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
void _endOfTestVerifications() {
verifyTickersWereDisposed('at the end of the test');
_verifySemanticsHandlesWereDisposed();
}
void _verifySemanticsHandlesWereDisposed() {
if (binding.pipelineOwner.semanticsOwner != null) {
throw new FlutterError(
'A SemanticsHandle was active at the end of the test.\n'
'All SemanticsHandle instances must be disposed by calling dispose() on '
'the SemanticsHandle. If your test uses SemanticsTester, it is '
'sufficient to call dispose() on SemanticsTester. Otherwise, the '
'existing handle will leak into another test and alter its behavior.'
);
}
}
/// Returns the TestTextInput singleton.
......
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