blob: 5eb247f59c695541bc2d683e90ce10f85b2f1575 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'semantics_tester.dart';
void main() {
setUp(() {
debugResetSemanticsIdCounter();
});
testWidgets('Semantics shutdown and restart', (WidgetTester tester) async {
SemanticsTester? semantics = SemanticsTester(tester);
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
label: 'test1',
textDirection: TextDirection.ltr,
),
],
);
await tester.pumpWidget(
Semantics(
label: 'test1',
textDirection: TextDirection.ltr,
child: Container(),
),
);
expect(semantics, hasSemantics(
expectedSemantics,
ignoreTransform: true,
ignoreRect: true,
ignoreId: true,
));
semantics.dispose();
semantics = null;
expect(tester.binding.hasScheduledFrame, isFalse);
semantics = SemanticsTester(tester);
expect(tester.binding.hasScheduledFrame, isTrue);
await tester.pump();
expect(semantics, hasSemantics(
expectedSemantics,
ignoreTransform: true,
ignoreRect: true,
ignoreId: true,
));
semantics.dispose();
}, semanticsEnabled: false);
testWidgets('Detach and reattach assert', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final GlobalKey key = GlobalKey();
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
label: 'test1',
child: Semantics(
key: key,
container: true,
label: 'test2a',
child: Container(),
),
),
));
expect(semantics, hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
label: 'test1',
children: <TestSemantics>[
TestSemantics(
label: 'test2a',
),
],
),
],
),
ignoreId: true,
ignoreRect: true,
ignoreTransform: true,
));
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
label: 'test1',
child: Semantics(
container: true,
label: 'middle',
child: Semantics(
key: key,
container: true,
label: 'test2b',
child: Container(),
),
),
),
));
expect(semantics, hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
label: 'test1',
children: <TestSemantics>[
TestSemantics(
label: 'middle',
children: <TestSemantics>[
TestSemantics(
label: 'test2b',
),
],
),
],
),
],
),
ignoreId: true,
ignoreRect: true,
ignoreTransform: true,
));
semantics.dispose();
});
testWidgets('Semantics and Directionality - RTL', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: Semantics(
label: 'test1',
child: Container(),
),
),
);
expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.rtl));
semantics.dispose();
});
testWidgets('Semantics and Directionality - LTR', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
label: 'test1',
child: Container(),
),
),
);
expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.ltr));
semantics.dispose();
});
testWidgets('Semantics and Directionality - cannot override RTL with LTR', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
label: 'test1',
textDirection: TextDirection.ltr,
),
],
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: Semantics(
label: 'test1',
textDirection: TextDirection.ltr,
child: Container(),
),
),
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics and Directionality - cannot override LTR with RTL', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
label: 'test1',
textDirection: TextDirection.rtl,
),
],
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
label: 'test1',
textDirection: TextDirection.rtl,
child: Container(),
),
),
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics label and hint', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
label: 'label',
hint: 'hint',
value: 'value',
child: Container(),
),
),
);
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
label: 'label',
hint: 'hint',
value: 'value',
textDirection: TextDirection.ltr,
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics hints can merge', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
container: true,
child: Column(
children: <Widget>[
Semantics(
hint: 'hint one',
),
Semantics(
hint: 'hint two',
),
],
),
),
),
);
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
hint: 'hint one\nhint two',
textDirection: TextDirection.ltr,
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics values do not merge', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
container: true,
child: Column(
children: <Widget>[
Semantics(
value: 'value one',
child: const SizedBox(
height: 10.0,
width: 10.0,
),
),
Semantics(
value: 'value two',
child: const SizedBox(
height: 10.0,
width: 10.0,
),
),
],
),
),
),
);
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
children: <TestSemantics>[
TestSemantics(
value: 'value one',
textDirection: TextDirection.ltr,
),
TestSemantics(
value: 'value two',
textDirection: TextDirection.ltr,
),
],
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics value and hint can merge', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
container: true,
child: Column(
children: <Widget>[
Semantics(
hint: 'hint',
),
Semantics(
value: 'value',
),
],
),
),
),
);
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
hint: 'hint',
value: 'value',
textDirection: TextDirection.ltr,
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics tagForChildren works', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
container: true,
tagForChildren: const SemanticsTag('custom tag'),
child: Column(
children: <Widget>[
Semantics(
container: true,
child: const Text('child 1'),
),
Semantics(
container: true,
child: const Text('child 2'),
),
],
),
),
),
);
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
children: <TestSemantics>[
TestSemantics(
label: 'child 1',
tags: <SemanticsTag>[const SemanticsTag('custom tag')],
textDirection: TextDirection.ltr,
),
TestSemantics(
label: 'child 2',
tags: <SemanticsTag>[const SemanticsTag('custom tag')],
textDirection: TextDirection.ltr,
),
],
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics widget supports all actions', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final List<SemanticsAction> performedActions = <SemanticsAction>[];
await tester.pumpWidget(
Semantics(
container: true,
onDismiss: () => performedActions.add(SemanticsAction.dismiss),
onTap: () => performedActions.add(SemanticsAction.tap),
onLongPress: () => performedActions.add(SemanticsAction.longPress),
onScrollLeft: () => performedActions.add(SemanticsAction.scrollLeft),
onScrollRight: () => performedActions.add(SemanticsAction.scrollRight),
onScrollUp: () => performedActions.add(SemanticsAction.scrollUp),
onScrollDown: () => performedActions.add(SemanticsAction.scrollDown),
onIncrease: () => performedActions.add(SemanticsAction.increase),
onDecrease: () => performedActions.add(SemanticsAction.decrease),
onCopy: () => performedActions.add(SemanticsAction.copy),
onCut: () => performedActions.add(SemanticsAction.cut),
onPaste: () => performedActions.add(SemanticsAction.paste),
onMoveCursorForwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorForwardByCharacter),
onMoveCursorBackwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorBackwardByCharacter),
onSetSelection: (TextSelection _) => performedActions.add(SemanticsAction.setSelection),
onSetText: (String _) => performedActions.add(SemanticsAction.setText),
onDidGainAccessibilityFocus: () => performedActions.add(SemanticsAction.didGainAccessibilityFocus),
onDidLoseAccessibilityFocus: () => performedActions.add(SemanticsAction.didLoseAccessibilityFocus),
),
);
final Set<SemanticsAction> allActions = SemanticsAction.values.values.toSet()
..remove(SemanticsAction.moveCursorForwardByWord)
..remove(SemanticsAction.moveCursorBackwardByWord)
..remove(SemanticsAction.customAction) // customAction is not user-exposed.
..remove(SemanticsAction.showOnScreen); // showOnScreen is not user-exposed
const int expectedId = 1;
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
id: expectedId,
rect: TestSemantics.fullScreen,
actions: allActions.fold<int>(0, (int previous, SemanticsAction action) => previous | action.index),
),
],
);
expect(semantics, hasSemantics(expectedSemantics));
// Do the actions work?
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;
int expectedLength = 1;
for (final SemanticsAction action in allActions) {
switch (action) {
case SemanticsAction.moveCursorBackwardByCharacter:
case SemanticsAction.moveCursorForwardByCharacter:
semanticsOwner.performAction(expectedId, action, true);
break;
case SemanticsAction.setSelection:
semanticsOwner.performAction(expectedId, action, <dynamic, dynamic>{
'base': 4,
'extent': 5,
});
break;
case SemanticsAction.setText:
semanticsOwner.performAction(expectedId, action, 'text');
break;
case SemanticsAction.copy:
case SemanticsAction.customAction:
case SemanticsAction.cut:
case SemanticsAction.decrease:
case SemanticsAction.didGainAccessibilityFocus:
case SemanticsAction.didLoseAccessibilityFocus:
case SemanticsAction.dismiss:
case SemanticsAction.increase:
case SemanticsAction.longPress:
case SemanticsAction.moveCursorBackwardByWord:
case SemanticsAction.moveCursorForwardByWord:
case SemanticsAction.paste:
case SemanticsAction.scrollDown:
case SemanticsAction.scrollLeft:
case SemanticsAction.scrollRight:
case SemanticsAction.scrollUp:
case SemanticsAction.showOnScreen:
case SemanticsAction.tap:
semanticsOwner.performAction(expectedId, action);
break;
}
expect(performedActions.length, expectedLength);
expect(performedActions.last, action);
expectedLength += 1;
}
semantics.dispose();
});
testWidgets('Semantics widget supports all flags', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
// Note: checked state and toggled state are mutually exclusive.
await tester.pumpWidget(
Semantics(
key: const Key('a'),
container: true,
explicitChildNodes: true,
// flags
enabled: true,
hidden: true,
checked: true,
selected: true,
button: true,
slider: true,
keyboardKey: true,
link: true,
textField: true,
readOnly: true,
focused: true,
focusable: true,
inMutuallyExclusiveGroup: true,
header: true,
obscured: true,
multiline: true,
scopesRoute: true,
namesRoute: true,
image: true,
liveRegion: true,
),
);
final List<SemanticsFlag> flags = SemanticsFlag.values.values.toList();
flags
..remove(SemanticsFlag.hasToggledState)
..remove(SemanticsFlag.isToggled)
..remove(SemanticsFlag.hasImplicitScrolling);
TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
rect: TestSemantics.fullScreen,
flags: flags,
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
await tester.pumpWidget(Semantics(
key: const Key('b'),
container: true,
scopesRoute: false,
));
expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
rect: TestSemantics.fullScreen,
flags: <SemanticsFlag>[],
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
await tester.pumpWidget(
Semantics(
key: const Key('c'),
toggled: true,
),
);
expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
rect: TestSemantics.fullScreen,
flags: <SemanticsFlag>[
SemanticsFlag.hasToggledState,
SemanticsFlag.isToggled,
],
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
semantics.dispose();
});
testWidgets('Actions can be replaced without triggering semantics update', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
int semanticsUpdateCount = 0;
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
},
);
final List<String> performedActions = <String>[];
await tester.pumpWidget(
Semantics(
container: true,
onTap: () => performedActions.add('first'),
),
);
const int expectedId = 1;
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
id: expectedId,
rect: TestSemantics.fullScreen,
actions: SemanticsAction.tap.index,
),
],
);
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;
expect(semantics, hasSemantics(expectedSemantics));
semanticsOwner.performAction(expectedId, SemanticsAction.tap);
expect(semanticsUpdateCount, 1);
expect(performedActions, <String>['first']);
semanticsUpdateCount = 0;
performedActions.clear();
// Updating existing handler should not trigger semantics update
await tester.pumpWidget(
Semantics(
container: true,
onTap: () => performedActions.add('second'),
),
);
expect(semantics, hasSemantics(expectedSemantics));
semanticsOwner.performAction(expectedId, SemanticsAction.tap);
expect(semanticsUpdateCount, 0);
expect(performedActions, <String>['second']);
semanticsUpdateCount = 0;
performedActions.clear();
// Adding a handler works
await tester.pumpWidget(
Semantics(
container: true,
onTap: () => performedActions.add('second'),
onLongPress: () => performedActions.add('longPress'),
),
);
final TestSemantics expectedSemanticsWithLongPress = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
id: expectedId,
rect: TestSemantics.fullScreen,
actions: SemanticsAction.tap.index | SemanticsAction.longPress.index,
),
],
);
expect(semantics, hasSemantics(expectedSemanticsWithLongPress));
semanticsOwner.performAction(expectedId, SemanticsAction.longPress);
expect(semanticsUpdateCount, 1);
expect(performedActions, <String>['longPress']);
semanticsUpdateCount = 0;
performedActions.clear();
// Removing a handler works
await tester.pumpWidget(
Semantics(
container: true,
onTap: () => performedActions.add('second'),
),
);
expect(semantics, hasSemantics(expectedSemantics));
expect(semanticsUpdateCount, 1);
handle.dispose();
semantics.dispose();
});
testWidgets('onTapHint and onLongPressHint create custom actions', (WidgetTester tester) async {
final SemanticsHandle semantics = tester.ensureSemantics();
await tester.pumpWidget(Semantics(
container: true,
onTap: () { },
onTapHint: 'test',
));
expect(tester.getSemantics(find.byType(Semantics)), matchesSemantics(
hasTapAction: true,
onTapHint: 'test',
));
await tester.pumpWidget(Semantics(
container: true,
onLongPress: () { },
onLongPressHint: 'foo',
));
expect(tester.getSemantics(find.byType(Semantics)), matchesSemantics(
hasLongPressAction: true,
onLongPressHint: 'foo',
));
semantics.dispose();
});
testWidgets('CustomSemanticsActions can be added to a Semantics widget', (WidgetTester tester) async {
final SemanticsHandle semantics = tester.ensureSemantics();
await tester.pumpWidget(Semantics(
container: true,
customSemanticsActions: <CustomSemanticsAction, VoidCallback>{
const CustomSemanticsAction(label: 'foo'): () { },
const CustomSemanticsAction(label: 'bar'): () { },
},
));
expect(tester.getSemantics(find.byType(Semantics)), matchesSemantics(
customActions: <CustomSemanticsAction>[
const CustomSemanticsAction(label: 'bar'),
const CustomSemanticsAction(label: 'foo'),
],
));
semantics.dispose();
});
testWidgets('Increased/decreased values are annotated', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
container: true,
value: '10s',
increasedValue: '11s',
decreasedValue: '9s',
onIncrease: () => () { },
onDecrease: () => () { },
),
),
);
expect(semantics, hasSemantics(TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
actions: SemanticsAction.increase.index | SemanticsAction.decrease.index,
textDirection: TextDirection.ltr,
value: '10s',
increasedValue: '11s',
decreasedValue: '9s',
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics widgets built in a widget tree are sorted properly', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
int semanticsUpdateCount = 0;
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
},
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
sortKey: const CustomSortKey(0.0),
explicitChildNodes: true,
child: Column(
children: <Widget>[
Semantics(sortKey: const CustomSortKey(3.0), child: const Text('Label 1')),
Semantics(sortKey: const CustomSortKey(2.0), child: const Text('Label 2')),
Semantics(
sortKey: const CustomSortKey(1.0),
explicitChildNodes: true,
child: Row(
children: <Widget>[
Semantics(sortKey: const OrdinalSortKey(3.0), child: const Text('Label 3')),
Semantics(sortKey: const OrdinalSortKey(2.0), child: const Text('Label 4')),
Semantics(sortKey: const OrdinalSortKey(1.0), child: const Text('Label 5')),
],
),
),
],
),
),
),
);
expect(semanticsUpdateCount, 1);
expect(semantics, hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
children: <TestSemantics>[
TestSemantics(
id: 4,
children: <TestSemantics>[
TestSemantics(
id: 7,
label: r'Label 5',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 6,
label: r'Label 4',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 5,
label: r'Label 3',
textDirection: TextDirection.ltr,
),
],
),
TestSemantics(
id: 3,
label: r'Label 2',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 2,
label: r'Label 1',
textDirection: TextDirection.ltr,
),
],
),
],
),
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 = SemanticsTester(tester);
int semanticsUpdateCount = 0;
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
},
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Row(
children: <Widget>[
Semantics(
sortKey: const CustomSortKey(3.0),
child: const Text('Label 1'),
),
Semantics(
sortKey: const CustomSortKey(1.0),
child: const Text('Label 2'),
),
Semantics(
sortKey: const CustomSortKey(2.0),
child: const Text('Label 3'),
),
],
),
),
);
expect(semanticsUpdateCount, 1);
expect(semantics, hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 2,
label: r'Label 2',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 3,
label: r'Label 3',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 1,
label: r'Label 1',
textDirection: TextDirection.ltr,
),
],
),
ignoreTransform: true,
ignoreRect: true,
));
handle.dispose();
semantics.dispose();
});
testWidgets('Semantics widgets without sort orders are sorted properly', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
int semanticsUpdateCount = 0;
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
},
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Column(
children: <Widget>[
const Text('Label 1'),
const Text('Label 2'),
Row(
children: const <Widget>[
Text('Label 3'),
Text('Label 4'),
Text('Label 5'),
],
),
],
),
),
);
expect(semanticsUpdateCount, 1);
expect(semantics, hasSemantics(
TestSemantics(
children: <TestSemantics>[
TestSemantics(
label: r'Label 1',
textDirection: TextDirection.ltr,
),
TestSemantics(
label: r'Label 2',
textDirection: TextDirection.ltr,
),
TestSemantics(
label: r'Label 3',
textDirection: TextDirection.ltr,
),
TestSemantics(
label: r'Label 4',
textDirection: TextDirection.ltr,
),
TestSemantics(
label: r'Label 5',
textDirection: TextDirection.ltr,
),
],
),
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 = SemanticsTester(tester);
int semanticsUpdateCount = 0;
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
},
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Column(
children: <Widget>[
const Text('Label 1'),
const Text('Label 2'),
Transform.rotate(
angle: pi / 2.0,
child: Row(
children: const <Widget>[
Text('Label 3'),
Text('Label 4'),
Text('Label 5'),
],
),
),
],
),
),
);
expect(semanticsUpdateCount, 1);
expect(semantics, hasSemantics(
TestSemantics(
children: <TestSemantics>[
TestSemantics(
label: r'Label 1',
textDirection: TextDirection.ltr,
),
TestSemantics(
label: r'Label 2',
textDirection: TextDirection.ltr,
),
TestSemantics(
label: r'Label 3',
textDirection: TextDirection.ltr,
),
TestSemantics(
label: r'Label 4',
textDirection: TextDirection.ltr,
),
TestSemantics(
label: r'Label 5',
textDirection: TextDirection.ltr,
),
],
),
ignoreTransform: true,
ignoreRect: true,
ignoreId: true,
));
handle.dispose();
semantics.dispose();
});
testWidgets('Semantics widgets without sort orders are sorted properly when no Directionality is present', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
int semanticsUpdateCount = 0;
final SemanticsHandle handle = tester.binding.pipelineOwner.ensureSemantics(listener: () {
semanticsUpdateCount += 1;
});
await tester.pumpWidget(
Stack(
alignment: Alignment.center,
children: <Widget>[
// Set this up so that the placeholder takes up the whole screen,
// and place the positioned boxes so that if we traverse in the
// geometric order, we would go from box [4, 3, 2, 1, 0], but if we
// go in child order, then we go from box [4, 1, 2, 3, 0]. We're verifying
// that we go in child order here, not geometric order, since there
// is no directionality, so we don't have a geometric opinion about
// horizontal order. We do still want to sort vertically, however,
// which is why the order isn't [0, 1, 2, 3, 4].
Semantics(
button: true,
child: const Placeholder(),
),
Positioned(
top: 200.0,
left: 100.0,
child: Semantics( // Box 0
button: true,
child: const SizedBox(width: 30.0, height: 30.0),
),
),
Positioned(
top: 100.0,
left: 200.0,
child: Semantics( // Box 1
button: true,
child: const SizedBox(width: 30.0, height: 30.0),
),
),
Positioned(
top: 100.0,
left: 100.0,
child: Semantics( // Box 2
button: true,
child: const SizedBox(width: 30.0, height: 30.0),
),
),
Positioned(
top: 100.0,
left: 0.0,
child: Semantics( // Box 3
button: true,
child: const SizedBox(width: 30.0, height: 30.0),
),
),
Positioned(
top: 10.0,
left: 100.0,
child: Semantics( // Box 4
button: true,
child: const SizedBox(width: 30.0, height: 30.0),
),
),
],
),
);
expect(semanticsUpdateCount, 1);
expect(
semantics,
hasSemantics(
TestSemantics(
children: <TestSemantics>[
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
),
],
),
ignoreTransform: true,
ignoreRect: true,
ignoreId: true,
),
);
handle.dispose();
semantics.dispose();
});
testWidgets('Semantics excludeSemantics ignores children', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(Semantics(
label: 'label',
excludeSemantics: true,
textDirection: TextDirection.ltr,
child: Semantics(
label: 'other label',
textDirection: TextDirection.ltr,
),
));
expect(semantics, hasSemantics(
TestSemantics(
children: <TestSemantics>[
TestSemantics(
label: 'label',
textDirection: TextDirection.ltr,
),
],
),
ignoreId: true,
ignoreRect: true,
ignoreTransform: true,
));
semantics.dispose();
});
testWidgets('Can change handlers', (WidgetTester tester) async {
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onTap: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasTapAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onDismiss: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasDismissAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onLongPress: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasLongPressAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onScrollLeft: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasScrollLeftAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onScrollRight: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasScrollRightAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onScrollUp: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasScrollUpAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onScrollDown: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasScrollDownAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onIncrease: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasIncreaseAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onDecrease: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasDecreaseAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onCopy: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasCopyAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onCut: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasCutAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onPaste: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasPasteAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onSetSelection: (TextSelection _) {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasSetSelectionAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onDidGainAccessibilityFocus: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasDidGainAccessibilityFocusAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
onDidLoseAccessibilityFocus: () {},
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
hasDidLoseAccessibilityFocusAction: true,
textDirection: TextDirection.ltr,
));
await tester.pumpWidget(Semantics(
container: true,
label: 'foo',
textDirection: TextDirection.ltr,
));
expect(tester.getSemantics(find.bySemanticsLabel('foo')), matchesSemantics(
label: 'foo',
textDirection: TextDirection.ltr,
));
});
}
class CustomSortKey extends OrdinalSortKey {
const CustomSortKey(double order, {String? name}) : super(order, name: name);
}