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.
......
......@@ -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,
......
......@@ -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,
),
],
......
......@@ -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