Move key event and semantics related method from WidgetTester to WidgetController (#62362)
This commit is contained in:
parent
0a69e810b1
commit
d053a4d00a
@ -7,9 +7,11 @@ import 'dart:async';
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'all_elements.dart';
|
||||
import 'event_simulation.dart';
|
||||
import 'finders.dart';
|
||||
import 'test_async_utils.dart';
|
||||
import 'test_pointer.dart';
|
||||
@ -686,10 +688,121 @@ abstract class WidgetController {
|
||||
return box.size;
|
||||
}
|
||||
|
||||
/// Simulates sending physical key down and up events through the system channel.
|
||||
///
|
||||
/// This only simulates key events coming from a physical keyboard, not from a
|
||||
/// soft keyboard.
|
||||
///
|
||||
/// Specify `platform` as one of the platforms allowed in
|
||||
/// [Platform.operatingSystem] to make the event appear to be from that type
|
||||
/// of system. Defaults to "android". Must not be null. Some platforms (e.g.
|
||||
/// Windows, iOS) are not yet supported.
|
||||
///
|
||||
/// Keys that are down when the test completes are cleared after each test.
|
||||
///
|
||||
/// This method sends both the key down and the key up events, to simulate a
|
||||
/// key press. To simulate individual down and/or up events, see
|
||||
/// [sendKeyDownEvent] and [sendKeyUpEvent].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// - [sendKeyDownEvent] to simulate only a key down event.
|
||||
/// - [sendKeyUpEvent] to simulate only a key up event.
|
||||
Future<void> sendKeyEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
|
||||
assert(platform != null);
|
||||
await simulateKeyDownEvent(key, platform: platform);
|
||||
// Internally wrapped in async guard.
|
||||
return simulateKeyUpEvent(key, platform: platform);
|
||||
}
|
||||
|
||||
/// Simulates sending a physical key down event through the system channel.
|
||||
///
|
||||
/// This only simulates key down events coming from a physical keyboard, not
|
||||
/// from a soft keyboard.
|
||||
///
|
||||
/// Specify `platform` as one of the platforms allowed in
|
||||
/// [Platform.operatingSystem] to make the event appear to be from that type
|
||||
/// of system. Defaults to "android". Must not be null. Some platforms (e.g.
|
||||
/// Windows, iOS) are not yet supported.
|
||||
///
|
||||
/// Keys that are down when the test completes are cleared after each test.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// - [sendKeyUpEvent] to simulate the corresponding key up event.
|
||||
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
|
||||
Future<void> sendKeyDownEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
|
||||
assert(platform != null);
|
||||
// Internally wrapped in async guard.
|
||||
return simulateKeyDownEvent(key, platform: platform);
|
||||
}
|
||||
|
||||
/// Simulates sending a physical key up event through the system channel.
|
||||
///
|
||||
/// This only simulates key up events coming from a physical keyboard,
|
||||
/// not from a soft keyboard.
|
||||
///
|
||||
/// Specify `platform` as one of the platforms allowed in
|
||||
/// [Platform.operatingSystem] to make the event appear to be from that type
|
||||
/// of system. Defaults to "android". May not be null.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// - [sendKeyDownEvent] to simulate the corresponding key down event.
|
||||
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
|
||||
Future<void> sendKeyUpEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
|
||||
assert(platform != null);
|
||||
// Internally wrapped in async guard.
|
||||
return simulateKeyUpEvent(key, platform: platform);
|
||||
}
|
||||
|
||||
/// Returns the rect of the given widget. This is only valid once
|
||||
/// the widget's render object has been laid out at least once.
|
||||
Rect getRect(Finder finder) => getTopLeft(finder) & getSize(finder);
|
||||
|
||||
/// Attempts to find the [SemanticsNode] of first result from `finder`.
|
||||
///
|
||||
/// If the object identified by the finder doesn't own it's semantic node,
|
||||
/// this will return the semantics data of the first ancestor with semantics.
|
||||
/// The ancestor's semantic data will include the child's as well as
|
||||
/// other nodes that have been merged together.
|
||||
///
|
||||
/// If the [SemanticsNode] of the object identified by the finder is
|
||||
/// force-merged into an ancestor (e.g. via the [MergeSemantics] widget)
|
||||
/// the node into which it is merged is returned. That node will include
|
||||
/// all the semantics information of the nodes merged into it.
|
||||
///
|
||||
/// Will throw a [StateError] if the finder returns more than one element or
|
||||
/// if no semantics are found or are not enabled.
|
||||
SemanticsNode getSemantics(Finder finder) {
|
||||
if (binding.pipelineOwner.semanticsOwner == null)
|
||||
throw StateError('Semantics are not enabled.');
|
||||
final Iterable<Element> candidates = finder.evaluate();
|
||||
if (candidates.isEmpty) {
|
||||
throw StateError('Finder returned no matching elements.');
|
||||
}
|
||||
if (candidates.length > 1) {
|
||||
throw StateError('Finder returned more than one element.');
|
||||
}
|
||||
final Element element = candidates.single;
|
||||
RenderObject renderObject = element.findRenderObject();
|
||||
SemanticsNode result = renderObject.debugSemantics;
|
||||
while (renderObject != null && (result == null || result.isMergedIntoParent)) {
|
||||
renderObject = renderObject?.parent as RenderObject;
|
||||
result = renderObject?.debugSemantics;
|
||||
}
|
||||
if (result == null)
|
||||
throw StateError('No Semantics data found.');
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Enable semantics in a test by creating a [SemanticsHandle].
|
||||
///
|
||||
/// The handle must be disposed at the end of the test.
|
||||
SemanticsHandle ensureSemantics() {
|
||||
return binding.pipelineOwner.ensureSemantics();
|
||||
}
|
||||
|
||||
/// Given a widget `W` specified by [finder] and a [Scrollable] widget `S` in
|
||||
/// its ancestry tree, this scrolls `S` so as to make `W` visible.
|
||||
///
|
||||
|
@ -20,7 +20,6 @@ import 'package:test_api/test_api.dart' as test_package;
|
||||
import 'all_elements.dart';
|
||||
import 'binding.dart';
|
||||
import 'controller.dart';
|
||||
import 'event_simulation.dart';
|
||||
import 'finders.dart';
|
||||
import 'matchers.dart';
|
||||
import 'restoration.dart';
|
||||
@ -560,6 +559,10 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
|
||||
/// frames) for `duration` amount of time, and then received a "Vsync" signal
|
||||
/// to paint the application.
|
||||
///
|
||||
/// For a [FakeAsync] environment (typically in `flutter test`), this advances
|
||||
/// time and timeout counting; for a live environment this delays `duration`
|
||||
/// time.
|
||||
///
|
||||
/// This is a convenience function that just calls
|
||||
/// [TestWidgetsFlutterBinding.pump].
|
||||
///
|
||||
@ -1055,74 +1058,6 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
|
||||
});
|
||||
}
|
||||
|
||||
/// Simulates sending physical key down and up events through the system channel.
|
||||
///
|
||||
/// This only simulates key events coming from a physical keyboard, not from a
|
||||
/// soft keyboard.
|
||||
///
|
||||
/// Specify `platform` as one of the platforms allowed in
|
||||
/// [Platform.operatingSystem] to make the event appear to be from that type
|
||||
/// of system. Defaults to "android". Must not be null. Some platforms (e.g.
|
||||
/// Windows, iOS) are not yet supported.
|
||||
///
|
||||
/// Keys that are down when the test completes are cleared after each test.
|
||||
///
|
||||
/// This method sends both the key down and the key up events, to simulate a
|
||||
/// key press. To simulate individual down and/or up events, see
|
||||
/// [sendKeyDownEvent] and [sendKeyUpEvent].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// - [sendKeyDownEvent] to simulate only a key down event.
|
||||
/// - [sendKeyUpEvent] to simulate only a key up event.
|
||||
Future<void> sendKeyEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
|
||||
assert(platform != null);
|
||||
await simulateKeyDownEvent(key, platform: platform);
|
||||
// Internally wrapped in async guard.
|
||||
return simulateKeyUpEvent(key, platform: platform);
|
||||
}
|
||||
|
||||
/// Simulates sending a physical key down event through the system channel.
|
||||
///
|
||||
/// This only simulates key down events coming from a physical keyboard, not
|
||||
/// from a soft keyboard.
|
||||
///
|
||||
/// Specify `platform` as one of the platforms allowed in
|
||||
/// [Platform.operatingSystem] to make the event appear to be from that type
|
||||
/// of system. Defaults to "android". Must not be null. Some platforms (e.g.
|
||||
/// Windows, iOS) are not yet supported.
|
||||
///
|
||||
/// Keys that are down when the test completes are cleared after each test.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// - [sendKeyUpEvent] to simulate the corresponding key up event.
|
||||
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
|
||||
Future<void> sendKeyDownEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
|
||||
assert(platform != null);
|
||||
// Internally wrapped in async guard.
|
||||
return simulateKeyDownEvent(key, platform: platform);
|
||||
}
|
||||
|
||||
/// Simulates sending a physical key up event through the system channel.
|
||||
///
|
||||
/// This only simulates key up events coming from a physical keyboard,
|
||||
/// not from a soft keyboard.
|
||||
///
|
||||
/// Specify `platform` as one of the platforms allowed in
|
||||
/// [Platform.operatingSystem] to make the event appear to be from that type
|
||||
/// of system. Defaults to "android". May not be null.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// - [sendKeyDownEvent] to simulate the corresponding key down event.
|
||||
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
|
||||
Future<void> sendKeyUpEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
|
||||
assert(platform != null);
|
||||
// Internally wrapped in async guard.
|
||||
return simulateKeyUpEvent(key, platform: platform);
|
||||
}
|
||||
|
||||
/// Makes an effort to dismiss the current page with a Material [Scaffold] or
|
||||
/// a [CupertinoPageScaffold].
|
||||
///
|
||||
@ -1139,49 +1074,6 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
|
||||
await tap(backButton);
|
||||
});
|
||||
}
|
||||
|
||||
/// Attempts to find the [SemanticsNode] of first result from `finder`.
|
||||
///
|
||||
/// If the object identified by the finder doesn't own it's semantic node,
|
||||
/// this will return the semantics data of the first ancestor with semantics.
|
||||
/// The ancestor's semantic data will include the child's as well as
|
||||
/// other nodes that have been merged together.
|
||||
///
|
||||
/// If the [SemanticsNode] of the object identified by the finder is
|
||||
/// force-merged into an ancestor (e.g. via the [MergeSemantics] widget)
|
||||
/// the node into which it is merged is returned. That node will include
|
||||
/// all the semantics information of the nodes merged into it.
|
||||
///
|
||||
/// Will throw a [StateError] if the finder returns more than one element or
|
||||
/// if no semantics are found or are not enabled.
|
||||
SemanticsNode getSemantics(Finder finder) {
|
||||
if (binding.pipelineOwner.semanticsOwner == null)
|
||||
throw StateError('Semantics are not enabled.');
|
||||
final Iterable<Element> candidates = finder.evaluate();
|
||||
if (candidates.isEmpty) {
|
||||
throw StateError('Finder returned no matching elements.');
|
||||
}
|
||||
if (candidates.length > 1) {
|
||||
throw StateError('Finder returned more than one element.');
|
||||
}
|
||||
final Element element = candidates.single;
|
||||
RenderObject renderObject = element.findRenderObject();
|
||||
SemanticsNode result = renderObject.debugSemantics;
|
||||
while (renderObject != null && (result == null || result.isMergedIntoParent)) {
|
||||
renderObject = renderObject?.parent as RenderObject;
|
||||
result = renderObject?.debugSemantics;
|
||||
}
|
||||
if (result == null)
|
||||
throw StateError('No Semantics data found.');
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Enable semantics in a test by creating a [SemanticsHandle].
|
||||
///
|
||||
/// The handle must be disposed at the end of the test.
|
||||
SemanticsHandle ensureSemantics() {
|
||||
return binding.pipelineOwner.ensureSemantics();
|
||||
}
|
||||
}
|
||||
|
||||
typedef _TickerDisposeCallback = void Function(_TestTicker ticker);
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/semantics.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@ -22,6 +23,140 @@ class TestDragData {
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('getSemanticsData', () {
|
||||
testWidgets('throws when there are no semantics', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Text('hello'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(() => tester.getSemantics(find.text('hello')), throwsStateError);
|
||||
}, semanticsEnabled: false);
|
||||
|
||||
testWidgets('throws when there are multiple results from the finder', (WidgetTester tester) async {
|
||||
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Row(
|
||||
children: const <Widget>[
|
||||
Text('hello'),
|
||||
Text('hello'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(() => tester.getSemantics(find.text('hello')), throwsStateError);
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Returns the correct SemanticsData', (WidgetTester tester) async {
|
||||
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Container(
|
||||
child: OutlineButton(
|
||||
onPressed: () { },
|
||||
child: const Text('hello'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final SemanticsNode node = tester.getSemantics(find.text('hello'));
|
||||
final SemanticsData semantics = node.getSemanticsData();
|
||||
expect(semantics.label, 'hello');
|
||||
expect(semantics.hasAction(SemanticsAction.tap), true);
|
||||
expect(semantics.hasFlag(SemanticsFlag.isButton), true);
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Can enable semantics for tests via semanticsEnabled', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Container(
|
||||
child: OutlineButton(
|
||||
onPressed: () { },
|
||||
child: const Text('hello'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final SemanticsNode node = tester.getSemantics(find.text('hello'));
|
||||
final SemanticsData semantics = node.getSemanticsData();
|
||||
expect(semantics.label, 'hello');
|
||||
expect(semantics.hasAction(SemanticsAction.tap), true);
|
||||
expect(semantics.hasFlag(SemanticsFlag.isButton), true);
|
||||
}, semanticsEnabled: true);
|
||||
|
||||
testWidgets('Returns merged SemanticsData', (WidgetTester tester) async {
|
||||
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
||||
const Key key = Key('test');
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Semantics(
|
||||
label: 'A',
|
||||
child: Semantics(
|
||||
label: 'B',
|
||||
child: Semantics(
|
||||
key: key,
|
||||
label: 'C',
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final SemanticsNode node = tester.getSemantics(find.byKey(key));
|
||||
final SemanticsData semantics = node.getSemanticsData();
|
||||
expect(semantics.label, 'A\nB\nC');
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Does not return partial semantics', (WidgetTester tester) async {
|
||||
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
||||
final Key key = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MergeSemantics(
|
||||
child: Semantics(
|
||||
container: true,
|
||||
label: 'A',
|
||||
child: Semantics(
|
||||
container: true,
|
||||
key: key,
|
||||
label: 'B',
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final SemanticsNode node = tester.getSemantics(find.byKey(key));
|
||||
final SemanticsData semantics = node.getSemanticsData();
|
||||
expect(semantics.label, 'A\nB');
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'WidgetTester.drag must break the offset into multiple parallel components if '
|
||||
'the drag goes outside the touch slop values',
|
||||
|
@ -24,140 +24,6 @@ const List<Widget> fooBarTexts = <Text>[
|
||||
];
|
||||
|
||||
void main() {
|
||||
group('getSemanticsData', () {
|
||||
testWidgets('throws when there are no semantics', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Text('hello'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(() => tester.getSemantics(find.text('hello')), throwsStateError);
|
||||
}, semanticsEnabled: false);
|
||||
|
||||
testWidgets('throws when there are multiple results from the finder', (WidgetTester tester) async {
|
||||
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Row(
|
||||
children: const <Widget>[
|
||||
Text('hello'),
|
||||
Text('hello'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(() => tester.getSemantics(find.text('hello')), throwsStateError);
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Returns the correct SemanticsData', (WidgetTester tester) async {
|
||||
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Container(
|
||||
child: OutlineButton(
|
||||
onPressed: () { },
|
||||
child: const Text('hello'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final SemanticsNode node = tester.getSemantics(find.text('hello'));
|
||||
final SemanticsData semantics = node.getSemanticsData();
|
||||
expect(semantics.label, 'hello');
|
||||
expect(semantics.hasAction(SemanticsAction.tap), true);
|
||||
expect(semantics.hasFlag(SemanticsFlag.isButton), true);
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Can enable semantics for tests via semanticsEnabled', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Container(
|
||||
child: OutlineButton(
|
||||
onPressed: () { },
|
||||
child: const Text('hello'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final SemanticsNode node = tester.getSemantics(find.text('hello'));
|
||||
final SemanticsData semantics = node.getSemanticsData();
|
||||
expect(semantics.label, 'hello');
|
||||
expect(semantics.hasAction(SemanticsAction.tap), true);
|
||||
expect(semantics.hasFlag(SemanticsFlag.isButton), true);
|
||||
}, semanticsEnabled: true);
|
||||
|
||||
testWidgets('Returns merged SemanticsData', (WidgetTester tester) async {
|
||||
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
||||
const Key key = Key('test');
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Semantics(
|
||||
label: 'A',
|
||||
child: Semantics(
|
||||
label: 'B',
|
||||
child: Semantics(
|
||||
key: key,
|
||||
label: 'C',
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final SemanticsNode node = tester.getSemantics(find.byKey(key));
|
||||
final SemanticsData semantics = node.getSemanticsData();
|
||||
expect(semantics.label, 'A\nB\nC');
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Does not return partial semantics', (WidgetTester tester) async {
|
||||
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
||||
final Key key = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MergeSemantics(
|
||||
child: Semantics(
|
||||
container: true,
|
||||
label: 'A',
|
||||
child: Semantics(
|
||||
container: true,
|
||||
key: key,
|
||||
label: 'B',
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final SemanticsNode node = tester.getSemantics(find.byKey(key));
|
||||
final SemanticsData semantics = node.getSemanticsData();
|
||||
expect(semantics.label, 'A\nB');
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
group('expectLater', () {
|
||||
testWidgets('completes when matcher completes', (WidgetTester tester) async {
|
||||
final Completer<void> completer = Completer<void>();
|
||||
|
Loading…
x
Reference in New Issue
Block a user