blob: db3920f83a4f2b128e1047cee30b8d1e6428ff43 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@TestOn('!chrome')
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../services/fake_platform_views.dart';
void main() {
group('AndroidView', () {
testWidgets('Create Android view', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
);
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
]),
);
});
testWidgets('Create Android view with params', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
creationParams: 'creation parameters',
creationParamsCodec: StringCodec(),
),
),
),
);
final FakeAndroidPlatformView fakeView = viewsController.views.first;
final Uint8List rawCreationParams = fakeView.creationParams;
final ByteData byteData = ByteData.view(
rawCreationParams.buffer,
rawCreationParams.offsetInBytes,
rawCreationParams.lengthInBytes,
);
final dynamic actualParams = const StringCodec().decodeMessage(byteData);
expect(actualParams, 'creation parameters');
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr, fakeView.creationParams),
]),
);
});
testWidgets('Zero sized Android view is not created', (WidgetTester tester) async {
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 0.0,
height: 0.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
);
expect(
viewsController.views,
isEmpty,
);
});
testWidgets('Resize Android view', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
);
viewsController.resizeCompleter = Completer<void>();
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 100.0,
height: 50.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
);
final Layer textureParentLayer = tester.layers[tester.layers.length - 2];
expect(textureParentLayer, isInstanceOf<ClipRectLayer>());
final ClipRectLayer clipRect = textureParentLayer;
expect(clipRect.clipRect, const Rect.fromLTWH(0.0, 0.0, 100.0, 50.0));
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
]),
);
viewsController.resizeCompleter.complete();
await tester.pump();
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(100.0, 50.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
]),
);
});
testWidgets('Change Android view type', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.registerViewType('maps');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
);
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'maps', layoutDirection: TextDirection.ltr),
),
),
);
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 2, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
]),
);
});
testWidgets('Dispose Android view', (WidgetTester tester) async {
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
);
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
),
),
);
expect(
viewsController.views,
isEmpty,
);
});
testWidgets('Android view survives widget tree change', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
final GlobalKey key = GlobalKey();
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr, key: key),
),
),
);
await tester.pumpWidget(
Center(
child: Container(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr, key: key),
),
),
),
);
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
]),
);
});
testWidgets('Android view gets touch events', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr,),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(
viewsController.motionEvents[currentViewId + 1],
orderedEquals(<FakeAndroidMotionEvent>[
const FakeAndroidMotionEvent(
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
const FakeAndroidMotionEvent(
AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 50.0)]),
]),
);
});
testWidgets('Android view transparent hit test behavior', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
int numPointerDownsOnParent = 0;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (PointerDownEvent e) {
numPointerDownsOnParent++;
},
),
const Positioned(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
hitTestBehavior: PlatformViewHitTestBehavior.transparent,
layoutDirection: TextDirection.ltr,
),
),
),
],
),
),
);
await tester.startGesture(const Offset(50.0, 50.0));
expect(
viewsController.motionEvents[currentViewId + 1],
isNull,
);
expect(
numPointerDownsOnParent,
1,
);
});
testWidgets('Android view translucent hit test behavior', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
int numPointerDownsOnParent = 0;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (PointerDownEvent e) {
numPointerDownsOnParent++;
},
),
const Positioned(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
hitTestBehavior: PlatformViewHitTestBehavior.translucent,
layoutDirection: TextDirection.ltr,
),
),
),
],
),
),
);
await tester.startGesture(const Offset(50.0, 50.0));
expect(
viewsController.motionEvents[currentViewId + 1],
orderedEquals(<FakeAndroidMotionEvent>[
const FakeAndroidMotionEvent(
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
]),
);
expect(
numPointerDownsOnParent,
1,
);
});
testWidgets('Android view opaque hit test behavior', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
int numPointerDownsOnParent = 0;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (PointerDownEvent e) {
numPointerDownsOnParent++;
},
),
const Positioned(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
layoutDirection: TextDirection.ltr,
),
),
),
],
),
),
);
await tester.startGesture(const Offset(50.0, 50.0));
expect(
viewsController.motionEvents[currentViewId + 1],
orderedEquals(<FakeAndroidMotionEvent>[
const FakeAndroidMotionEvent(
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
]),
);
expect(
numPointerDownsOnParent,
0,
);
});
testWidgets('Android view touch events are in virtual display\'s coordinate system', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: Container(
margin: const EdgeInsets.all(10.0),
child: const SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(
viewsController.motionEvents[currentViewId + 1],
orderedEquals(<FakeAndroidMotionEvent>[
const FakeAndroidMotionEvent(
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(40.0, 40.0)]),
const FakeAndroidMotionEvent(
AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(40.0, 40.0)]),
]),
);
});
testWidgets('Android view directionality', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('maps');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'maps', layoutDirection: TextDirection.rtl),
),
),
);
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionRtl),
]),
);
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'maps', layoutDirection: TextDirection.ltr),
),
),
);
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
]),
);
});
testWidgets('Android view ambient directionality', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('maps');
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'maps'),
),
),
),
);
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionRtl),
]),
);
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'maps'),
),
),
),
);
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
]),
);
});
testWidgets('Android view can lose gesture arenas', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
bool verticalDragAcceptedByParent = false;
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: Container(
margin: const EdgeInsets.all(10.0),
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
child: const SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await gesture.up();
expect(verticalDragAcceptedByParent, true);
expect(
viewsController.motionEvents[currentViewId + 1],
isNull,
);
});
testWidgets('Android view gesture recognizers', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
bool verticalDragAcceptedByParent = false;
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<VerticalDragGestureRecognizer>(
() {
return VerticalDragGestureRecognizer()
..onStart = (_) {}; // Add callback to enable recognizer
},
),
},
layoutDirection: TextDirection.ltr,
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await gesture.up();
expect(verticalDragAcceptedByParent, false);
expect(
viewsController.motionEvents[currentViewId + 1],
orderedEquals(<FakeAndroidMotionEvent>[
const FakeAndroidMotionEvent(
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
const FakeAndroidMotionEvent(
AndroidViewController.kActionMove, <int>[0], <Offset>[Offset(50.0, 150.0)]),
const FakeAndroidMotionEvent(
AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 150.0)]),
]),
);
});
testWidgets(
'Android view can claim gesture after all pointers are up', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
bool verticalDragAcceptedByParent = false;
// The long press recognizer rejects the gesture after the AndroidView gets the pointer up event.
// This test makes sure that the Android view can win the gesture after it got the pointer up event.
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
onLongPress: () { },
child: const SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(verticalDragAcceptedByParent, false);
expect(
viewsController.motionEvents[currentViewId + 1],
orderedEquals(<FakeAndroidMotionEvent>[
const FakeAndroidMotionEvent(
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
const FakeAndroidMotionEvent(
AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 50.0)]),
]),
);
});
testWidgets('Android view rebuilt during gesture', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await tester.pumpWidget(
const Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
),
),
),
);
await gesture.up();
expect(
viewsController.motionEvents[currentViewId + 1],
orderedEquals(<FakeAndroidMotionEvent>[
const FakeAndroidMotionEvent(
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
const FakeAndroidMotionEvent(
AndroidViewController.kActionMove, <int>[0], <Offset>[Offset(50.0, 150.0)]),
const FakeAndroidMotionEvent(
AndroidViewController.kActionUp, <int>[0], <Offset>[Offset(50.0, 150.0)]),
]),
);
});
testWidgets('Android view with eager gesture recognizer', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) { },
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
() => EagerGestureRecognizer(),
),
},
layoutDirection: TextDirection.ltr,
),
),
),
),
);
await tester.startGesture(const Offset(50.0, 50.0));
// Normally (without the eager gesture recognizer) after just the pointer down event
// no gesture arena member will claim the arena (so no motion events will be dispatched to
// the Android view). Here we assert that with the eager recognizer in the gesture team the
// pointer down event is immediately dispatched.
expect(
viewsController.motionEvents[currentViewId + 1],
orderedEquals(<FakeAndroidMotionEvent>[
const FakeAndroidMotionEvent(
AndroidViewController.kActionDown, <int>[0], <Offset>[Offset(50.0, 50.0)]),
]),
);
});
// This test makes sure it doesn't crash.
// https://github.com/flutter/flutter/issues/21514
testWidgets('RenderAndroidView reconstructed with same gestureRecognizers does not crash', (WidgetTester tester) async {
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
final AndroidView androidView = AndroidView(
viewType: 'webview',
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<EagerGestureRecognizer>(
() => EagerGestureRecognizer(),
),
},
layoutDirection: TextDirection.ltr,
);
await tester.pumpWidget(androidView);
await tester.pumpWidget(const SizedBox.shrink());
await tester.pumpWidget(androidView);
});
testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
int factoryInvocationCount = 0;
final ValueGetter<EagerGestureRecognizer> constructRecognizer = () {
factoryInvocationCount += 1;
return EagerGestureRecognizer();
};
await tester.pumpWidget(
AndroidView(
viewType: 'webview',
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<EagerGestureRecognizer>(constructRecognizer),
},
layoutDirection: TextDirection.ltr,
),
);
await tester.pumpWidget(
AndroidView(
viewType: 'webview',
hitTestBehavior: PlatformViewHitTestBehavior.translucent,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<EagerGestureRecognizer>(constructRecognizer),
},
layoutDirection: TextDirection.ltr,
),
);
expect(factoryInvocationCount, 1);
});
testWidgets('AndroidView has correct semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
expect(currentViewId, greaterThanOrEqualTo(0));
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.createCompleter = Completer<void>();
await tester.pumpWidget(
Semantics(
container: true,
child: const Align(
alignment: Alignment.bottomRight,
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
),
),
),
),
);
final SemanticsNode semantics = tester.getSemantics(find.byType(AndroidView));
// Platform view has not been created yet, no platformViewId.
expect(semantics.platformViewId, null);
expect(semantics.rect, const Rect.fromLTWH(0, 0, 200, 100));
// A 200x100 rect positioned at bottom right of a 800x600 box.
expect(semantics.transform, Matrix4.translationValues(600, 500, 0));
expect(semantics.childrenCount, 0);
viewsController.createCompleter.complete();
await tester.pumpAndSettle();
expect(semantics.platformViewId, currentViewId + 1);
expect(semantics.rect, const Rect.fromLTWH(0, 0, 200, 100));
// A 200x100 rect positioned at bottom right of a 800x600 box.
expect(semantics.transform, Matrix4.translationValues(600, 500, 0));
expect(semantics.childrenCount, 0);
handle.dispose();
});
testWidgets('AndroidView can take input focus', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.createCompleter = Completer<void>();
final GlobalKey containerKey = GlobalKey();
await tester.pumpWidget(
Center(
child: Column(
children: <Widget>[
const SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
Focus(
debugLabel: 'container',
child: Container(key: containerKey),
),
],
),
),
);
final Focus androidViewFocusWidget =
tester.widget(
find.descendant(
of: find.byType(AndroidView),
matching: find.byType(Focus)
)
);
final Element containerElement = tester.element(find.byKey(containerKey));
final FocusNode androidViewFocusNode = androidViewFocusWidget.focusNode;
final FocusNode containerFocusNode = Focus.of(containerElement);
containerFocusNode.requestFocus();
await tester.pump();
expect(containerFocusNode.hasFocus, isTrue);
expect(androidViewFocusNode.hasFocus, isFalse);
viewsController.invokeViewFocused(currentViewId + 1);
await tester.pump();
expect(containerFocusNode.hasFocus, isFalse);
expect(androidViewFocusNode.hasFocus, isTrue);
});
testWidgets('AndroidView sets a platform view text input client when focused', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.createCompleter = Completer<void>();
final GlobalKey containerKey = GlobalKey();
await tester.pumpWidget(
Center(
child: Column(
children: <Widget>[
const SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
Focus(
debugLabel: 'container',
child: Container(key: containerKey),
),
],
),
),
);
viewsController.createCompleter.complete();
final Element containerElement = tester.element(find.byKey(containerKey));
final FocusNode containerFocusNode = Focus.of(containerElement);
containerFocusNode.requestFocus();
await tester.pump();
int lastPlatformViewTextClient;
SystemChannels.textInput.setMockMethodCallHandler((MethodCall call) {
if (call.method == 'TextInput.setPlatformViewClient') {
lastPlatformViewTextClient = call.arguments;
}
return null;
});
viewsController.invokeViewFocused(currentViewId + 1);
await tester.pump();
expect(lastPlatformViewTextClient, currentViewId + 1);
});
testWidgets('AndroidView clears platform focus when unfocused', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.createCompleter = Completer<void>();
final GlobalKey containerKey = GlobalKey();
await tester.pumpWidget(
Center(
child: Column(
children: <Widget>[
const SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
Focus(
debugLabel: 'container',
child: Container(key: containerKey),
),
],
),
),
);
viewsController.createCompleter.complete();
final Element containerElement = tester.element(find.byKey(containerKey));
final FocusNode containerFocusNode = Focus.of(containerElement);
containerFocusNode.requestFocus();
await tester.pump();
viewsController.invokeViewFocused(currentViewId + 1);
await tester.pump();
viewsController.lastClearedFocusViewId = null;
containerFocusNode.requestFocus();
await tester.pump();
expect(viewsController.lastClearedFocusViewId, currentViewId + 1);
});
});
group('UiKitView', () {
testWidgets('Create UIView', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
);
expect(
viewsController.views,
unorderedEquals(<FakeUiKitView>[
FakeUiKitView(currentViewId + 1, 'webview'),
]),
);
});
testWidgets('Change UIView view type', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.registerViewType('maps');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
);
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(viewType: 'maps', layoutDirection: TextDirection.ltr),
),
),
);
expect(
viewsController.views,
unorderedEquals(<FakeUiKitView>[
FakeUiKitView(currentViewId + 2, 'maps'),
]),
);
});
testWidgets('Dispose UIView ', (WidgetTester tester) async {
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
);
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
),
),
);
expect(
viewsController.views,
isEmpty,
);
});
testWidgets('Dispose UIView before creation completed ', (WidgetTester tester) async {
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.creationDelay = Completer<void>();
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
);
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
),
),
);
viewsController.creationDelay.complete();
expect(
viewsController.views,
isEmpty,
);
});
testWidgets('UIView survives widget tree change', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
final GlobalKey key = GlobalKey();
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr, key: key),
),
),
);
await tester.pumpWidget(
Center(
child: Container(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr, key: key),
),
),
),
);
expect(
viewsController.views,
unorderedEquals(<FakeUiKitView>[
FakeUiKitView(currentViewId + 1, 'webview'),
]),
);
});
testWidgets('Create UIView with params', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
creationParams: 'creation parameters',
creationParamsCodec: StringCodec(),
),
),
),
);
final FakeUiKitView fakeView = viewsController.views.first;
final Uint8List rawCreationParams = fakeView.creationParams;
final ByteData byteData = ByteData.view(
rawCreationParams.buffer,
rawCreationParams.offsetInBytes,
rawCreationParams.lengthInBytes,
);
final dynamic actualParams = const StringCodec().decodeMessage(byteData);
expect(actualParams, 'creation parameters');
expect(
viewsController.views,
unorderedEquals(<FakeUiKitView>[
FakeUiKitView(currentViewId + 1, 'webview', fakeView.creationParams),
]),
);
});
testWidgets('UiKitView accepts gestures', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr,),
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
});
testWidgets('UiKitView transparent hit test behavior', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
int numPointerDownsOnParent = 0;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (PointerDownEvent e) {
numPointerDownsOnParent++;
},
),
const Positioned(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(
viewType: 'webview',
hitTestBehavior: PlatformViewHitTestBehavior.transparent,
layoutDirection: TextDirection.ltr,
),
),
),
],
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
expect(numPointerDownsOnParent, 1);
});
testWidgets('UiKitView translucent hit test behavior', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
int numPointerDownsOnParent = 0;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (PointerDownEvent e) {
numPointerDownsOnParent++;
},
),
const Positioned(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(
viewType: 'webview',
hitTestBehavior: PlatformViewHitTestBehavior.translucent,
layoutDirection: TextDirection.ltr,
),
),
),
],
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(numPointerDownsOnParent, 1);
});
testWidgets('UiKitView opaque hit test behavior', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
int numPointerDownsOnParent = 0;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: <Widget>[
Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (PointerDownEvent e) {
numPointerDownsOnParent++;
},
),
const Positioned(
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(
viewType: 'webview',
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
layoutDirection: TextDirection.ltr,
),
),
),
],
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(numPointerDownsOnParent, 0);
});
testWidgets('UiKitView can lose gesture arenas', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
bool verticalDragAcceptedByParent = false;
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: Container(
margin: const EdgeInsets.all(10.0),
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
child: const SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
),
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await gesture.up();
expect(verticalDragAcceptedByParent, true);
expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
expect(viewsController.gesturesRejected[currentViewId + 1], 1);
});
testWidgets('UiKitView gesture recognizers', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
bool verticalDragAcceptedByParent = false;
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(
viewType: 'webview',
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<VerticalDragGestureRecognizer>(
() {
return VerticalDragGestureRecognizer()
..onStart = (_) {}; // Add callback to enable recognizer
},
),
},
layoutDirection: TextDirection.ltr,
),
),
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await gesture.up();
expect(verticalDragAcceptedByParent, false);
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
testWidgets('UiKitView can claim gesture after all pointers are up', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
bool verticalDragAcceptedByParent = false;
// The long press recognizer rejects the gesture after the AndroidView gets the pointer up event.
// This test makes sure that the Android view can win the gesture after it got the pointer up event.
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
onLongPress: () { },
child: const SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
),
),
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(verticalDragAcceptedByParent, false);
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
testWidgets('UiKitView rebuilt during gesture', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
),
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await tester.pumpWidget(
const Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
),
),
),
);
await gesture.up();
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
testWidgets('UiKitView with eager gesture recognizer', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) { },
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(
viewType: 'webview',
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
() => EagerGestureRecognizer(),
),
},
layoutDirection: TextDirection.ltr,
),
),
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
await tester.startGesture(const Offset(50.0, 50.0));
// Normally (without the eager gesture recognizer) after just the pointer down event
// no gesture arena member will claim the arena (so no motion events will be dispatched to
// the Android view). Here we assert that with the eager recognizer in the gesture team the
// pointer down event is immediately dispatched.
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
testWidgets('UiKitView rejects gestures absorbed by siblings', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
Stack(
alignment: Alignment.topLeft,
children: <Widget>[
const UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
Container(
color: const Color.fromARGB(255, 255, 255, 255),
width: 100,
height: 100,
),
],
)
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(viewsController.gesturesRejected[currentViewId + 1], 1);
expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
});
testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
int factoryInvocationCount = 0;
final ValueGetter<EagerGestureRecognizer> constructRecognizer = () {
factoryInvocationCount += 1;
return EagerGestureRecognizer();
};
await tester.pumpWidget(
UiKitView(
viewType: 'webview',
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<EagerGestureRecognizer>(constructRecognizer),
},
layoutDirection: TextDirection.ltr,
),
);
await tester.pumpWidget(
UiKitView(
viewType: 'webview',
hitTestBehavior: PlatformViewHitTestBehavior.translucent,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<EagerGestureRecognizer>(constructRecognizer),
},
layoutDirection: TextDirection.ltr,
),
);
expect(factoryInvocationCount, 1);
});
testWidgets('UiKitView has correct semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
expect(currentViewId, greaterThanOrEqualTo(0));
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
Semantics(
container: true,
child: const Align(
alignment: Alignment.bottomRight,
child: SizedBox(
width: 200.0,
height: 100.0,
child: UiKitView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
),
),
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
final SemanticsNode semantics = tester.getSemantics(find.byType(UiKitView));
expect(semantics.platformViewId, currentViewId + 1);
expect(semantics.rect, const Rect.fromLTWH(0, 0, 200, 100));
// A 200x100 rect positioned at bottom right of a 800x600 box.
expect(semantics.transform, Matrix4.translationValues(600, 500, 0));
expect(semantics.childrenCount, 0);
handle.dispose();
});
});
group('Common PlatformView', () {
FakePlatformViewController controller;
setUp((){
controller = FakePlatformViewController(0);
});
testWidgets('PlatformViewSurface should create platform view layer', (WidgetTester tester) async {
final PlatformViewSurface surface = PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},);
await tester.pumpWidget(surface);
final PlatformViewLayer layer = tester.layers.firstWhere((Layer layer){
return layer is PlatformViewLayer;
});
expect(layer, isNotNull);
});
testWidgets('PlatformViewSurface can lose gesture arenas', (WidgetTester tester) async {
bool verticalDragAcceptedByParent = false;
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: Container(
margin: const EdgeInsets.all(10.0),
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque),
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await gesture.up();
expect(verticalDragAcceptedByParent, true);
expect(
controller.dispatchedPointerEvents,
isEmpty,
);
});
testWidgets('PlatformViewSurface gesture recognizers dispatch events', (WidgetTester tester) async {
bool verticalDragAcceptedByParent = false;
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<VerticalDragGestureRecognizer>(
() {
return VerticalDragGestureRecognizer()
..onStart = (_) {}; // Add callback to enable recognizer
},
),
},
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await gesture.up();
expect(verticalDragAcceptedByParent, false);
expect(
controller.dispatchedPointerEvents.length,
3,
);
});
testWidgets(
'PlatformViewSurface can claim gesture after all pointers are up', (WidgetTester tester) async {
bool verticalDragAcceptedByParent = false;
// The long press recognizer rejects the gesture after the PlatformViewSurface gets the pointer up event.
// This test makes sure that the PlatformViewSurface can win the gesture after it got the pointer up event.
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
onLongPress: () { },
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(verticalDragAcceptedByParent, false);
expect(
controller.dispatchedPointerEvents.length,
2,
);
});
testWidgets('PlatformViewSurface rebuilt during gesture', (WidgetTester tester) async {
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
),
),
),
);
await gesture.up();
expect(
controller.dispatchedPointerEvents.length,
3,
);
});
testWidgets('PlatformViewSurface with eager gesture recognizer', (WidgetTester tester) async {
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) { },
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
() => EagerGestureRecognizer(),
),
},
),
),
),
),
);
await tester.startGesture(const Offset(50.0, 50.0));
// Normally (without the eager gesture recognizer) after just the pointer down event
// no gesture arena member will claim the arena (so no motion events will be dispatched to
// the PlatformViewSurface). Here we assert that with the eager recognizer in the gesture team the
// pointer down event is immediately dispatched.
expect(
controller.dispatchedPointerEvents.length,
1,
);
});
testWidgets('PlatformViewRenderBox reconstructed with same gestureRecognizers', (WidgetTester tester) async {
int factoryInvocationCount = 0;
final ValueGetter<EagerGestureRecognizer> constructRecognizer = () {
++ factoryInvocationCount;
return EagerGestureRecognizer();
};
final PlatformViewSurface platformViewSurface = PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
constructRecognizer,
),
});
await tester.pumpWidget(platformViewSurface);
await tester.pumpWidget(const SizedBox.shrink());
await tester.pumpWidget(platformViewSurface);
expect(factoryInvocationCount, 2);
});
testWidgets('PlatformViewSurface rebuilt with same gestureRecognizers', (WidgetTester tester) async {
int factoryInvocationCount = 0;
final ValueGetter<EagerGestureRecognizer> constructRecognizer = () {
++ factoryInvocationCount;
return EagerGestureRecognizer();
};
await tester.pumpWidget(
PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
constructRecognizer,
),
})
);
await tester.pumpWidget(
PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
constructRecognizer,
),
})
);
expect(factoryInvocationCount, 1);
});
testWidgets('PlatformViewLink Widget init, should create a SizedBox widget before onPlatformViewCreated and a PlatformViewSurface after', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
int createdPlatformViewId;
PlatformViewCreatedCallback onPlatformViewCreatedCallBack;
final PlatformViewLink platformViewLink = PlatformViewLink(onCreatePlatformView: (PlatformViewCreationParams params){
onPlatformViewCreatedCallBack = params.onPlatformViewCreated;
createdPlatformViewId = params.id;
return FakePlatformViewController(params.id);
}, surfaceFactory: (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface(
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
});
await tester.pumpWidget(platformViewLink);
final SizedBox sizedBox = tester.allWidgets.firstWhere((Widget widget) => widget is SizedBox);
expect(sizedBox, isNotNull);
onPlatformViewCreatedCallBack(createdPlatformViewId);
await tester.pump();
final PlatformViewSurface surface = tester.allWidgets.firstWhere((Widget widget) => widget is PlatformViewSurface);
expect(surface, isNotNull);
expect(createdPlatformViewId, currentViewId+1);
});
testWidgets('PlatformViewLink Widget dispose', (WidgetTester tester) async {
FakePlatformViewController disposedController;
final PlatformViewLink platformViewLink = PlatformViewLink(onCreatePlatformView: (PlatformViewCreationParams params){
disposedController = FakePlatformViewController(params.id);
params.onPlatformViewCreated(params.id);
return disposedController;
}, surfaceFactory: (BuildContext context,PlatformViewController controller) {
return PlatformViewSurface(
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
});
await tester.pumpWidget(platformViewLink);
await tester.pumpWidget(Container());
expect(disposedController.disposed, true);
});
testWidgets('PlatformViewLink widget survives widget tree change', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final List<int> ids = <int>[];
FakePlatformViewController controller;
PlatformViewLink createPlatformViewLink() {
return PlatformViewLink(
key: key,
onCreatePlatformView: (PlatformViewCreationParams params){
ids.add(params.id);
controller = FakePlatformViewController(params.id);
params.onPlatformViewCreated(params.id);
return controller;
},
surfaceFactory: (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface(
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
);
}
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: createPlatformViewLink(),
),
),
);
await tester.pumpWidget(
Center(
child: Container(
child: SizedBox(
width: 200.0,
height: 100.0,
child: createPlatformViewLink(),
),
),
),
);
expect(
ids,
unorderedEquals(<int>[
currentViewId+1,
]),
);
});
testWidgets('PlatformViewLink can take any widget to return in the SurfaceFactory', (WidgetTester tester) async {
final PlatformViewLink platformViewLink = PlatformViewLink(onCreatePlatformView: (PlatformViewCreationParams params){
params.onPlatformViewCreated(params.id);
return FakePlatformViewController(params.id);
}, surfaceFactory: (BuildContext context,PlatformViewController controller) {
return Container();
});
await tester.pumpWidget(platformViewLink);
final Container container = tester.allWidgets.firstWhere((Widget widget){
return widget is Container;
});
expect(container, isNotNull);
});
});
}