Unverified Commit e3da1bd7 authored by Tong Mu's avatar Tong Mu Committed by GitHub

Test WidgetTester handling test pointers (#83337)

Adds tests to the following behaviors, which have existed without tests:

- When tapping during live testing, a message is printed with widgets that contain the tap location.
- When tapping during live testing, a mark is displayed on screen on the tap location.
parent 4ddaa13d
......@@ -167,7 +167,7 @@ Future<void> main() async {
} else if (delays.last < delay) {
delays.last = delay;
}
tester.binding.handlePointerEvent(event, source: TestBindingEventSource.test);
tester.binding.handlePointerEventForSource(event, source: TestBindingEventSource.test);
}
}
......
......@@ -42,7 +42,7 @@ class KeyboardKeysCodeGenerator extends BaseCodeGenerator {
final String firstComment = _wrapString('Represents the location of the '
'"${entry.commentName}" key on a generalized keyboard.');
final String otherComments = _wrapString('See the function '
'[RawKeyEvent.physicalKey] for more information.');
'[KeyEvent.physical] for more information.');
definitions.write('''
$firstComment ///
......@@ -67,7 +67,7 @@ $otherComments static const PhysicalKeyboardKey ${entry.constantName} = Physica
final StringBuffer definitions = StringBuffer();
void printKey(int flutterId, String constantName, String commentName, {String? otherComments}) {
final String firstComment = _wrapString('Represents the logical "$commentName" key on the keyboard.');
otherComments ??= _wrapString('See the function [RawKeyEvent.logicalKey] for more information.');
otherComments ??= _wrapString('See the function [KeyEvent.logical] for more information.');
definitions.write('''
$firstComment ///
......
......@@ -11,7 +11,7 @@ void main() {
* because [matchesGoldenFile] does not use Skia Gold in its native package.
*/
testWidgets('correctly records frames', (WidgetTester tester) async {
testWidgets('correctly records frames using display', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size);
await tester.pumpFrames(
......@@ -40,8 +40,9 @@ void main() {
const Duration(milliseconds: 100),
);
final Widget display = await builder.display();
await tester.binding.setSurfaceSize(builder.sheetSize());
// This test verifies deprecated methods.
final Widget display = await builder.display(); // ignore: deprecated_member_use
await tester.binding.setSurfaceSize(builder.sheetSize()); // ignore: deprecated_member_use
await tester.pumpWidget(display);
await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.records.png'));
......@@ -57,12 +58,80 @@ void main() {
const Duration(milliseconds: 200),
);
final Widget display = await builder.display();
await tester.binding.setSurfaceSize(builder.sheetSize(maxWidth: 80));
// This test verifies deprecated methods.
final Widget display = await builder.display(); // ignore: deprecated_member_use
await tester.binding.setSurfaceSize(builder.sheetSize(maxWidth: 80)); // ignore: deprecated_member_use
await tester.pumpWidget(display);
await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.wraps.png'));
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/42767
testWidgets('correctly records frames using collate', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size);
await tester.pumpFrames(
builder.record(
const _DecuplePixels(Duration(seconds: 1)),
),
const Duration(milliseconds: 200),
const Duration(milliseconds: 100),
);
await tester.pumpFrames(
builder.record(
const _DecuplePixels(Duration(seconds: 1)),
recording: false,
),
const Duration(milliseconds: 200),
const Duration(milliseconds: 100),
);
await tester.pumpFrames(
builder.record(
const _DecuplePixels(Duration(seconds: 1)),
recording: true,
),
const Duration(milliseconds: 400),
const Duration(milliseconds: 100),
);
await expectLater(
builder.collate(5),
matchesGoldenFile('test.animation_sheet_builder.collate.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/42767
testWidgets('use allLayers to record out-of-subtree contents', (WidgetTester tester) async {
final AnimationSheetBuilder builder = AnimationSheetBuilder(
frameSize: const Size(8, 2),
allLayers: true,
);
// The `record` (sized 8, 2) is placed on top of `_DecuplePixels`
// (sized 12, 3), aligned at its top left.
await tester.pumpFrames(
Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
const _DecuplePixels(Duration(seconds: 1)),
Align(
alignment: Alignment.topLeft,
child: builder.record(Container()),
),
],
),
),
const Duration(milliseconds: 600),
const Duration(milliseconds: 100),
);
await expectLater(
builder.collate(5),
matchesGoldenFile('test.animation_sheet_builder.out_of_tree.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/42767
}
// An animation of a yellow pixel moving from left to right, in a container of
......
// 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 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
/*
* Here lies golden tests for packages/flutter_test/lib/src/binding.dart
* because [matchesGoldenFile] does not use Skia Gold in its native package.
*/
LiveTestWidgetsFlutterBinding();
testWidgets('Should show event indicator for pointer events', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(200, 200), allLayers: true);
final Widget target = Container(
padding: const EdgeInsets.fromLTRB(20, 10, 25, 20),
child: animationSheet.record(
MaterialApp(
home: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 128, 128, 128),
border: Border.all(color: const Color.fromARGB(255, 0, 0, 0)),
),
child: Center(
child: GestureDetector(
onTap: () {},
child: const Text('Test'),
),
),
),
),
),
);
await tester.pumpWidget(target);
await tester.pumpFrames(target, const Duration(milliseconds: 50));
final TestGesture gesture1 = await tester.createGesture();
await gesture1.down(tester.getCenter(find.byType(Text)) + const Offset(10, 10));
await tester.pumpFrames(target, const Duration(milliseconds: 100));
final TestGesture gesture2 = await tester.createGesture();
await gesture2.down(tester.getTopLeft(find.byType(Text)) + const Offset(30, -10));
await gesture1.moveBy(const Offset(50, 50));
await tester.pumpFrames(target, const Duration(milliseconds: 100));
await gesture1.up();
await gesture2.up();
await tester.pumpFrames(target, const Duration(milliseconds: 50));
await expectLater(
animationSheet.collate(6),
matchesGoldenFile('LiveBinding.press.animation.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/42767
}
......@@ -772,13 +772,8 @@ void main() {
),
), const Duration(seconds: 2));
tester.binding.setSurfaceSize(animationSheet.sheetSize());
final Widget display = await animationSheet.display();
await tester.pumpWidget(display);
await expectLater(
find.byWidget(display),
await animationSheet.collate(20),
matchesGoldenFile('material.circular_progress_indicator.indeterminate.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/42767
......
......@@ -457,22 +457,35 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// events from the device).
Offset localToGlobal(Offset point) => point;
// The source of the current pointer event.
//
// The [pointerEventSource] is set as the `source` parameter of
// [handlePointerEvent] and can be used in the immediate enclosing
// [dispatchEvent].
/// The source of the current pointer event.
///
/// The [pointerEventSource] is set as the `source` parameter of
/// [handlePointerEventForSource] and can be used in the immediate enclosing
/// [dispatchEvent].
TestBindingEventSource get pointerEventSource => _pointerEventSource;
TestBindingEventSource _pointerEventSource = TestBindingEventSource.device;
@override
void handlePointerEvent(
/// Dispatch an event to the targets found by a hit test on its position,
/// and remember its source as [pointerEventSource].
///
/// This method sets [pointerEventSource] to `source`, runs
/// [handlePointerEvent], then resets [pointerEventSource] to the previous
/// value.
void handlePointerEventForSource(
PointerEvent event, {
TestBindingEventSource source = TestBindingEventSource.device,
}) {
withPointerEventSource(source, () => handlePointerEvent(event));
}
/// Sets [pointerEventSource] to `source`, runs `task`, then resets `source`
/// to the previous value.
@protected
void withPointerEventSource(TestBindingEventSource source, VoidCallback task) {
final TestBindingEventSource previousSource = source;
_pointerEventSource = source;
try {
super.handlePointerEvent(event);
task();
} finally {
_pointerEventSource = previousSource;
}
......@@ -1490,11 +1503,8 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
/// Apart from forwarding the event to [GestureBinding.dispatchEvent],
/// This also paint all events that's down on the screen.
@override
void handlePointerEvent(
PointerEvent event, {
TestBindingEventSource source = TestBindingEventSource.device,
}) {
switch (source) {
void handlePointerEvent(PointerEvent event) {
switch (pointerEventSource) {
case TestBindingEventSource.test:
final _LiveTestPointerRecord? record = _liveTestRenderView._pointers[event.pointer];
if (record != null) {
......@@ -1509,18 +1519,21 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
);
_handleViewNeedsPaint();
}
super.handlePointerEvent(event, source: TestBindingEventSource.test);
super.handlePointerEvent(event);
break;
case TestBindingEventSource.device:
if (deviceEventDispatcher != null)
super.handlePointerEvent(event, source: TestBindingEventSource.device);
if (deviceEventDispatcher != null) {
withPointerEventSource(TestBindingEventSource.device,
() => super.handlePointerEvent(event)
);
}
break;
}
}
@override
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
switch (_pointerEventSource) {
switch (pointerEventSource) {
case TestBindingEventSource.test:
super.dispatchEvent(event, hitTestResult);
break;
......
......@@ -550,7 +550,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
// Flush all past events
handleTimeStampDiff.add(-timeDiff);
for (final PointerEvent event in record.events) {
binding.handlePointerEvent(event, source: TestBindingEventSource.test);
binding.handlePointerEventForSource(event, source: TestBindingEventSource.test);
}
} else {
await binding.pump();
......@@ -559,7 +559,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
binding.clock.now().difference(startTime) - record.timeDelay,
);
for (final PointerEvent event in record.events) {
binding.handlePointerEvent(event, source: TestBindingEventSource.test);
binding.handlePointerEventForSource(event, source: TestBindingEventSource.test);
}
}
}
......@@ -788,7 +788,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
@override
Future<void> sendEventToBinding(PointerEvent event) {
return TestAsyncUtils.guard<void>(() async {
binding.handlePointerEvent(event, source: TestBindingEventSource.test);
binding.handlePointerEventForSource(event, source: TestBindingEventSource.test);
});
}
......
// 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 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
final _MockLiveTestWidgetsFlutterBinding binding = _MockLiveTestWidgetsFlutterBinding();
testWidgets('Should print message on pointer events', (WidgetTester tester) async {
final List<String?> printedMessages = <String?>[];
int invocations = 0;
await tester.pumpWidget(
MaterialApp(
home: Center(
child: GestureDetector(
onTap: () {
invocations++;
},
child: const Text('Test'),
),
),
),
);
final Size windowCenter = tester.binding.window.physicalSize /
tester.binding.window.devicePixelRatio /
2;
final double windowCenterX = windowCenter.width;
final double windowCenterY = windowCenter.height;
final Offset widgetCenter = tester.getRect(find.byType(Text)).center;
expect(widgetCenter.dx, windowCenterX);
expect(widgetCenter.dy, windowCenterY);
await binding.collectDebugPrints(printedMessages, () async {
await tester.tap(find.byType(Text));
});
await tester.pump();
expect(invocations, 0);
expect(printedMessages, equals('''
Some possible finders for the widgets at Offset(400.0, 300.0):
find.text('Test')
find.widgetWithText(RawGestureDetector, 'Test')
find.byType(GestureDetector)
find.byType(Center)
find.widgetWithText(IgnorePointer, 'Test')
find.byType(FadeTransition)
find.byType(FractionalTranslation)
find.byType(SlideTransition)
find.widgetWithText(PrimaryScrollController, 'Test')
find.widgetWithText(PageStorage, 'Test')
find.widgetWithText(Offstage, 'Test')
'''.trim().split('\n')));
printedMessages.clear();
await binding.collectDebugPrints(printedMessages, () async {
await tester.tapAt(const Offset(1, 1));
});
expect(printedMessages, equals('''
Some possible finders for the widgets at Offset(1.0, 1.0):
find.byType(MouseRegion)
find.byType(ExcludeSemantics)
find.byType(BlockSemantics)
find.byType(ModalBarrier)
find.byType(Overlay)
'''.trim().split('\n')));
});
}
class _MockLiveTestWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding {
@override
TestBindingEventSource get pointerEventSource => TestBindingEventSource.device;
List<String?>? _storeDebugPrints;
@override
DebugPrintCallback get debugPrintOverride {
return _storeDebugPrints == null
? super.debugPrintOverride
: ((String? message, { int? wrapWidth }) => _storeDebugPrints!.add(message));
}
// Execute `task` while redirecting [debugPrint] to appending to `store`.
Future<void> collectDebugPrints(List<String?>? store, AsyncValueGetter<void> task) async {
_storeDebugPrints = store;
try {
await task();
} finally {
_storeDebugPrints = null;
}
}
}
......@@ -759,6 +759,8 @@ void main() {
expect(flutterErrorDetails.exception, isA<AssertionError>());
expect((flutterErrorDetails.exception as AssertionError).message, 'A Timer is still pending even after the widget tree was disposed.');
expect(binding.inTest, true);
binding.postTest();
});
});
}
......
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