Skip to content

Commit

Permalink
Add setSurfaceSize changes
Browse files Browse the repository at this point in the history
Fix

codelab works

Doc and print

Fix tests
  • Loading branch information
dkwingsmt committed Jul 16, 2021
1 parent 8cf1de3 commit 559b13a
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 50 deletions.
63 changes: 58 additions & 5 deletions packages/flutter/test/animation/live_binding_test.dart
Expand Up @@ -25,9 +25,12 @@ void main() {
border: Border.all(color: const Color.fromARGB(255, 0, 0, 0)),
),
child: Center(
child: GestureDetector(
onTap: () {},
child: const Text('Test'),
child: Material(
child: InkWell(
splashColor: Colors.blue,
child: const SizedBox(width: 40, height: 40),
onTap: () {},
),
),
),
),
Expand All @@ -40,12 +43,12 @@ void main() {
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 gesture1.down(tester.getCenter(find.byType(InkWell)) + 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 gesture2.down(tester.getTopLeft(find.byType(InkWell)) + const Offset(30, -10));
await gesture1.moveBy(const Offset(50, 50));

await tester.pumpFrames(target, const Duration(milliseconds: 100));
Expand All @@ -58,4 +61,54 @@ void main() {
matchesGoldenFile('LiveBinding.press.animation.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/42767

testWidgets('Should show event indicator for pointer events with setSurfaceSize', (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: Material(
child: InkWell(
splashColor: Colors.blue,
child: const SizedBox(width: 40, height: 40),
onTap: () {},
),
),
),
),
),
),
);

await tester.binding.setSurfaceSize(const Size(300, 300));
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(InkWell)) + 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(InkWell)) + 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.2.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/42767
}
54 changes: 37 additions & 17 deletions packages/flutter_test/lib/src/binding.dart
Expand Up @@ -462,15 +462,38 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// The [pointerEventSource] is set as the `source` parameter of
/// [handlePointerEventForSource] and can be used in the immediate enclosing
/// [dispatchEvent].
///
/// When [handlePointerEvent] is called directly, [pointerEventSource]
/// is [TestBindingEventSource.device].
TestBindingEventSource get pointerEventSource => _pointerEventSource;
TestBindingEventSource _pointerEventSource = TestBindingEventSource.device;

/// 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
/// This method sets [pointerEventSource] to `source`, forwards the call to
/// [handlePointerEvent], then resets [pointerEventSource] to the previous
/// value.
///
/// If `source` is [TestBindingEventSource.device], then the `event`
/// is based in the global coordinate system and the event is likely
/// triggered by the user physically interacting with the screen during a
/// live test on a real device (see [LiveTestWidgetsFlutterBinding]).
///
/// If `source` is [TestBindingEventSource.test], then the `event`
/// is based in the local coordinate system and the event is likely
/// triggered by programatically simulated pointer event, such as:
///
/// * [WidgetController.tap] and alike methods, as well as directly using
/// [TestGesture]. They are usually used in
/// [AutomatedTestWidgetsFlutterBinding] but sometimes in live tests too.
/// * [WidgetController.timedDrag] and alike methods. They are usually used
/// in macrobenchmarks.
///
/// See also:
///
/// * [localToGlobal] and [globalToLocal] for transforming between coordinate
/// systems.
void handlePointerEventForSource(
PointerEvent event, {
TestBindingEventSource source = TestBindingEventSource.device,
Expand All @@ -482,7 +505,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// to the previous value.
@protected
void withPointerEventSource(TestBindingEventSource source, VoidCallback task) {
final TestBindingEventSource previousSource = source;
final TestBindingEventSource previousSource = _pointerEventSource;
_pointerEventSource = source;
try {
task();
Expand Down Expand Up @@ -1497,11 +1520,15 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
/// Events dispatched by [TestGesture] are not affected by this.
HitTestDispatcher? deviceEventDispatcher;


/// Dispatch an event to the targets found by a hit test on its position.
///
/// Apart from forwarding the event to [GestureBinding.dispatchEvent],
/// This also paint all events that's down on the screen.
/// If the [pointerEventSource] is [TestBindingEventSource.test], then
/// the event is forwarded to [GestureBinding.dispatchEvent] as normal,
/// and down events are also painted on the screen.
///
/// If the [pointerEventSource] is [TestBindingEventSource.device], then
/// the event, after being transformed to the local coordinate system, is
/// forwarded to [deviceEventDispatcher].
@override
void handlePointerEvent(PointerEvent event) {
switch (pointerEventSource) {
Expand All @@ -1523,8 +1550,9 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
break;
case TestBindingEventSource.device:
if (deviceEventDispatcher != null) {
final PointerEvent localEvent = event.copyWith(position: globalToLocal(event.position));
withPointerEventSource(TestBindingEventSource.device,
() => super.handlePointerEvent(event)
() => super.handlePointerEvent(localEvent)
);
}
break;
Expand All @@ -1538,9 +1566,10 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
super.dispatchEvent(event, hitTestResult);
break;
case TestBindingEventSource.device:
assert(hitTestResult != null);
assert(hitTestResult != null || event is PointerAddedEvent || event is PointerRemovedEvent);
assert(deviceEventDispatcher != null);
deviceEventDispatcher!.dispatchEvent(event, hitTestResult!);
if (hitTestResult != null)
deviceEventDispatcher!.dispatchEvent(event, hitTestResult);
break;
}
}
Expand Down Expand Up @@ -1775,15 +1804,6 @@ class _LiveTestRenderView extends RenderView {
onNeedPaint();
}

@override
bool hitTest(HitTestResult result, { required Offset position }) {
final Matrix4 transform = configuration.toHitTestMatrix();
final double det = transform.invert();
assert(det != 0.0);
position = MatrixUtils.transformPoint(transform, position);
return super.hitTest(result, position: position);
}

@override
void paint(PaintingContext context, Offset offset) {
assert(offset == Offset.zero);
Expand Down
27 changes: 19 additions & 8 deletions packages/flutter_test/lib/src/widget_tester.dart
Expand Up @@ -59,6 +59,17 @@ export 'package:test_api/test_api.dart' hide
/// Signature for callback to [testWidgets] and [benchmarkWidgets].
typedef WidgetTesterCallback = Future<void> Function(WidgetTester widgetTester);

// [Iterable.lastWhere] but returns null if not found.
T? _lastWhereOrNull<T>(Iterable<T> list, bool Function(T) condition) {
try {
return list.lastWhere(condition);
} catch(e) {
if (e is StateError) {
return null;
}
}
}

/// Runs the [callback] inside the Flutter test environment.
///
/// Use this function for testing custom [StatelessWidget]s and
Expand Down Expand Up @@ -800,15 +811,15 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
.map((HitTestEntry candidate) => candidate.target)
.whereType<RenderObject>()
.first;
final Element? innerTargetElement = collectAllElementsFrom(
binding.renderViewElement!,
skipOffstage: true,
).cast<Element?>().lastWhere(
(Element? element) => element!.renderObject == innerTarget,
orElse: () => null,
final Element? innerTargetElement = _lastWhereOrNull(
collectAllElementsFrom(
binding.renderViewElement!,
skipOffstage: true,
).cast<Element>(),
(Element element) => element.renderObject == innerTarget,
);
if (innerTargetElement == null) {
printToConsole('No widgets found at ${binding.globalToLocal(event.position)}.');
printToConsole('No widgets found at ${event.position}.');
return;
}
final List<Element> candidates = <Element>[];
Expand All @@ -821,7 +832,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
int numberOfWithTexts = 0;
int numberOfTypes = 0;
int totalNumber = 0;
printToConsole('Some possible finders for the widgets at ${binding.globalToLocal(event.position)}:');
printToConsole('Some possible finders for the widgets at ${event.position}:');
for (final Element element in candidates) {
if (totalNumber > 13) // an arbitrary number of finders that feels useful without being overwhelming
break;
Expand Down
91 changes: 71 additions & 20 deletions packages/flutter_test/test/widget_tester_live_device_test.dart
Expand Up @@ -6,6 +6,13 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

// Only check the initial lines of the message, since the message walks the
// entire widget tree back, and any changes to the widget tree break these
// tests if we check the entire message.
void _expectStartsWith(List<String?> actual, List<String?> matcher) {
expect(actual.sublist(0, matcher.length), equals(matcher));
}

void main() {
final _MockLiveTestWidgetsFlutterBinding binding = _MockLiveTestWidgetsFlutterBinding();

Expand All @@ -14,8 +21,9 @@ void main() {

int invocations = 0;
await tester.pumpWidget(
MaterialApp(
home: Center(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: GestureDetector(
onTap: () {
invocations++;
Expand All @@ -42,39 +50,82 @@ void main() {
await tester.pump();
expect(invocations, 0);

expect(printedMessages, equals('''
_expectStartsWith(printedMessages, '''
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(FocusTrap, 'Test')
find.widgetWithText(PrimaryScrollController, 'Test')
find.widgetWithText(PageStorage, 'Test')
'''.trim().split('\n'));
printedMessages.clear();

await binding.collectDebugPrints(printedMessages, () async {
await tester.tapAt(const Offset(1, 1));
});
expect(printedMessages, equals('''
No widgets found at Offset(1.0, 1.0).
'''.trim().split('\n')));
});

testWidgets('Should print message on pointer events with setSurfaceSize', (WidgetTester tester) async {
final List<String?> printedMessages = <String?>[];

int invocations = 0;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child:GestureDetector(
onTap: () {
invocations++;
},
child: const Text('Test'),
),
),
),
);

await tester.binding.setSurfaceSize(const Size(2000, 1800));
await tester.pump();

final Offset widgetCenter = tester.getRect(find.byType(Text)).center;
expect(widgetCenter.dx, 1000);
expect(widgetCenter.dy, 900);

await binding.collectDebugPrints(printedMessages, () async {
await tester.tap(find.byType(Text));
});
await tester.pump();
expect(invocations, 0);

_expectStartsWith(printedMessages, '''
Some possible finders for the widgets at Offset(1000.0, 900.0):
find.text('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)
No widgets found at Offset(1.0, 1.0).
'''.trim().split('\n')));
});
}

class _MockLiveTestWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding {
@override
TestBindingEventSource get pointerEventSource => TestBindingEventSource.device;
void handlePointerEventForSource(
PointerEvent event, {
TestBindingEventSource source = TestBindingEventSource.device,
}) {
// In this test we use `WidgetTester.tap` to simulate real device touches.
// `WidgetTester.tap` sends events in the local coordinate system, while
// real devices touches sends event in the global coordinate system.
// See the documentation of [handlePointerEventForSource] for details.
if (source == TestBindingEventSource.test) {
final PointerEvent globalEvent = event.copyWith(position: localToGlobal(event.position));
return super.handlePointerEventForSource(globalEvent, source: TestBindingEventSource.device);
}
return super.handlePointerEventForSource(event, source: source);
}

List<String?>? _storeDebugPrints;

Expand Down

0 comments on commit 559b13a

Please sign in to comment.