| // 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. |
| |
| @TestOn('!chrome') |
| |
| import 'dart:async'; |
| import 'dart:typed_data'; |
| import 'dart:ui'; |
| |
| 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, |
| null, |
| ), |
| ]), |
| ); |
| }); |
| |
| 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, |
| null, |
| 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, isA<ClipRectLayer>()); |
| final ClipRectLayer clipRect = textureParentLayer as ClipRectLayer; |
| 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, |
| null, |
| ), |
| ]), |
| ); |
| |
| viewsController.resizeCompleter!.complete(); |
| await tester.pump(); |
| |
| expect( |
| viewsController.views, |
| unorderedEquals(<FakeAndroidPlatformView>[ |
| FakeAndroidPlatformView( |
| currentViewId + 1, |
| 'webview', |
| const Size(100.0, 50.0), |
| AndroidViewController.kAndroidLayoutDirectionLtr, |
| null, |
| ), |
| ]), |
| ); |
| }); |
| |
| 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, |
| null, |
| ), |
| ]), |
| ); |
| }); |
| |
| 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: 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, |
| null, |
| ), |
| ]), |
| ); |
| }); |
| |
| 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', |
| 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, |
| null, |
| ), |
| ]), |
| ); |
| |
| 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, |
| null, |
| ), |
| ]), |
| ); |
| }); |
| |
| 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, |
| null, |
| ), |
| ]), |
| ); |
| |
| 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, |
| null, |
| ), |
| ]), |
| ); |
| }); |
| |
| 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 drag gesture recognizer', (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(); |
| }, |
| ), |
| }, |
| 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 long press gesture recognizer', (WidgetTester tester) async { |
| final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); |
| final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController(); |
| viewsController.registerViewType('webview'); |
| bool longPressAccessedByParent = false; |
| await tester.pumpWidget( |
| Align( |
| alignment: Alignment.topLeft, |
| child: GestureDetector( |
| onLongPress: () { |
| longPressAccessedByParent = true; |
| }, |
| child: SizedBox( |
| width: 200.0, |
| height: 100.0, |
| child: AndroidView( |
| viewType: 'webview', |
| gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ |
| Factory<LongPressGestureRecognizer>( |
| () { |
| return LongPressGestureRecognizer(); |
| }, |
| ), |
| }, |
| layoutDirection: TextDirection.ltr, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.longPressAt(const Offset(50.0, 50.0)); |
| |
| expect(longPressAccessedByParent, 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 tap gesture recognizer', (WidgetTester tester) async { |
| final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); |
| final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController(); |
| viewsController.registerViewType('webview'); |
| bool tapAccessedByParent = false; |
| await tester.pumpWidget( |
| Align( |
| alignment: Alignment.topLeft, |
| child: GestureDetector( |
| onTap: () { |
| tapAccessedByParent = true; |
| }, |
| child: SizedBox( |
| width: 200.0, |
| height: 100.0, |
| child: AndroidView( |
| viewType: 'webview', |
| gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ |
| Factory<TapGestureRecognizer>( |
| () { |
| return TapGestureRecognizer(); |
| }, |
| ), |
| }, |
| layoutDirection: TextDirection.ltr, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.tapAt(const Offset(50.0, 50.0)); |
| |
| expect(tapAccessedByParent, 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 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; |
| 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, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // Find the first _AndroidPlatformView widget inside of the AndroidView so |
| // that it finds the right RenderObject when looking for semantics. |
| final Finder semanticsFinder = find.byWidgetPredicate( |
| (Widget widget) { |
| return widget.runtimeType.toString() == '_AndroidPlatformView'; |
| }, |
| description: '_AndroidPlatformView widget inside AndroidView', |
| ); |
| final SemanticsNode semantics = tester.getSemantics(semanticsFinder.first); |
| |
| // 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(); |
| |
| late Map<String, dynamic> lastPlatformViewTextClient; |
| tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall call) { |
| if (call.method == 'TextInput.setPlatformViewClient') { |
| lastPlatformViewTextClient = call.arguments as Map<String, dynamic>; |
| } |
| return null; |
| }); |
| |
| viewsController.invokeViewFocused(currentViewId + 1); |
| await tester.pump(); |
| |
| expect(lastPlatformViewTextClient.containsKey('platformViewId'), true); |
| expect(lastPlatformViewTextClient['platformViewId'], currentViewId + 1); |
| |
| expect(lastPlatformViewTextClient.containsKey('usesVirtualDisplay'), true); |
| expect(lastPlatformViewTextClient['usesVirtualDisplay'], true); |
| }); |
| |
| 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); |
| }); |
| |
| testWidgets('can set and update clipBehavior', (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, |
| ), |
| ), |
| ), |
| ); |
| |
| // By default, clipBehavior should be Clip.hardEdge |
| final RenderAndroidView renderObject = tester.renderObject( |
| find.descendant( |
| of: find.byType(AndroidView), |
| matching: find.byWidgetPredicate( |
| (Widget widget) => widget.runtimeType.toString() == '_AndroidPlatformView', |
| ), |
| ), |
| ); |
| expect(renderObject.clipBehavior, equals(Clip.hardEdge)); |
| |
| for (final Clip clip in Clip.values) { |
| await tester.pumpWidget( |
| Center( |
| child: SizedBox( |
| width: 200.0, |
| height: 100.0, |
| child: AndroidView( |
| viewType: 'webview', |
| layoutDirection: TextDirection.ltr, |
| clipBehavior: clip, |
| ), |
| ), |
| ), |
| ); |
| expect(renderObject.clipBehavior, clip); |
| } |
| }); |
| |
| testWidgets('clip is handled correctly during resizing', (WidgetTester tester) async { |
| // Regressing test for https://github.com/flutter/flutter/issues/67343 |
| |
| final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController(); |
| viewsController.registerViewType('webview'); |
| |
| Widget buildView(double width, double height, Clip clipBehavior) { |
| return Center( |
| child: SizedBox( |
| width: width, |
| height: height, |
| child: AndroidView( |
| viewType: 'webview', |
| layoutDirection: TextDirection.ltr, |
| clipBehavior: clipBehavior, |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildView(200.0, 200.0, Clip.none)); |
| // Resize the view. |
| await tester.pumpWidget(buildView(100.0, 100.0, Clip.none)); |
| // No clip happen when the clip behavior is `Clip.none` . |
| expect(tester.layers.whereType<ClipRectLayer>(), hasLength(0)); |
| |
| // No clip when only the clip behavior changes while the size remains the same. |
| await tester.pumpWidget(buildView(100.0, 100.0, Clip.hardEdge)); |
| expect(tester.layers.whereType<ClipRectLayer>(), hasLength(0)); |
| |
| // Resize trigger clip when the clip behavior is not `Clip.none` . |
| await tester.pumpWidget(buildView(50.0, 100.0, Clip.hardEdge)); |
| expect(tester.layers.whereType<ClipRectLayer>(), hasLength(1)); |
| ClipRectLayer clipRectLayer = tester.layers.whereType<ClipRectLayer>().first; |
| expect(clipRectLayer.clipRect, const Rect.fromLTWH(0.0, 0.0, 50.0, 100.0)); |
| |
| await tester.pumpWidget(buildView(50.0, 50.0, Clip.hardEdge)); |
| expect(tester.layers.whereType<ClipRectLayer>(), hasLength(1)); |
| clipRectLayer = tester.layers.whereType<ClipRectLayer>().first; |
| expect(clipRectLayer.clipRect, const Rect.fromLTWH(0.0, 0.0, 50.0, 50.0)); |
| }); |
| }); |
| |
| group('AndroidViewSurface', () { |
| late FakeAndroidViewController controller; |
| |
| setUp(() { |
| controller = FakeAndroidViewController(0); |
| }); |
| |
| testWidgets('AndroidViewSurface sets pointTransformer of view controller', (WidgetTester tester) async { |
| final AndroidViewSurface surface = AndroidViewSurface( |
| controller: controller, |
| hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
| gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}, |
| ); |
| await tester.pumpWidget(surface); |
| expect(controller.pointTransformer, isNotNull); |
| }); |
| }); |
| |
| 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: 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', |
| 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 tap gesture recognizers', (WidgetTester tester) async { |
| final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); |
| final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController(); |
| viewsController.registerViewType('webview'); |
| bool gestureAcceptedByParent = false; |
| await tester.pumpWidget( |
| Align( |
| alignment: Alignment.topLeft, |
| child: GestureDetector( |
| onVerticalDragStart: (DragStartDetails d) { |
| gestureAcceptedByParent = true; |
| }, |
| child: SizedBox( |
| width: 200.0, |
| height: 100.0, |
| child: UiKitView( |
| viewType: 'webview', |
| gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ |
| Factory<VerticalDragGestureRecognizer>( |
| () { |
| return VerticalDragGestureRecognizer(); |
| }, |
| ), |
| }, |
| 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(gestureAcceptedByParent, false); |
| expect(viewsController.gesturesAccepted[currentViewId + 1], 1); |
| expect(viewsController.gesturesRejected[currentViewId + 1], 0); |
| }); |
| |
| testWidgets('UiKitView long press gesture recognizers', (WidgetTester tester) async { |
| final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); |
| final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController(); |
| viewsController.registerViewType('webview'); |
| bool gestureAcceptedByParent = false; |
| await tester.pumpWidget( |
| Align( |
| alignment: Alignment.topLeft, |
| child: GestureDetector( |
| onLongPress: () { |
| gestureAcceptedByParent = true; |
| }, |
| child: SizedBox( |
| width: 200.0, |
| height: 100.0, |
| child: UiKitView( |
| viewType: 'webview', |
| gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ |
| Factory<LongPressGestureRecognizer>( |
| () { |
| return LongPressGestureRecognizer(); |
| }, |
| ), |
| }, |
| 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.longPressAt(const Offset(50.0, 50.0)); |
| |
| expect(gestureAcceptedByParent, false); |
| expect(viewsController.gesturesAccepted[currentViewId + 1], 1); |
| expect(viewsController.gesturesRejected[currentViewId + 1], 0); |
| }); |
| |
| testWidgets('UiKitView drag 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<TapGestureRecognizer>( |
| () { |
| return TapGestureRecognizer(); |
| }, |
| ), |
| }, |
| 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.tapAt(const Offset(50.0, 50.0)); |
| |
| 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( |
| 'UiKitView rejects gestures absorbed by siblings if the touch is outside of the platform view bounds but inside platform view frame', |
| (WidgetTester tester) async { |
| // UiKitView is positioned at (left=0, top=100, right=300, bottom=600). |
| // Opaque container is on top of the UiKitView positioned at (left=0, top=500, right=300, bottom=600). |
| // Touch on (550, 150) is expected to be absorbed by the container. |
| final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); |
| final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController(); |
| viewsController.registerViewType('webview'); |
| |
| await tester.pumpWidget( |
| SizedBox( |
| width: 300, |
| height: 600, |
| child: Stack( |
| alignment: Alignment.topLeft, |
| children: <Widget>[ |
| Transform.translate( |
| offset: const Offset(0, 100), |
| child: const SizedBox( |
| width: 300, |
| height: 500, |
| child: UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr), |
| ), |
| ), |
| Transform.translate( |
| offset: const Offset(0, 500), |
| child: Container( |
| color: const Color.fromARGB(255, 255, 255, 255), |
| width: 300, |
| 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(150, 550)); |
| 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; |
| 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', () { |
| late 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); |
| expect(() => tester.layers.whereType<PlatformViewLayer>().first, returnsNormally); |
| }); |
| |
| 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(); |
| }, |
| ), |
| }, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| 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; |
| 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; |
| 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(); |
| late int createdPlatformViewId; |
| |
| late PlatformViewCreatedCallback onPlatformViewCreatedCallBack; |
| |
| final PlatformViewLink platformViewLink = PlatformViewLink( |
| viewType: 'webview', |
| 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); |
| expect(() => tester.allWidgets.whereType<SizedBox>().first, returnsNormally); |
| |
| onPlatformViewCreatedCallBack(createdPlatformViewId); |
| |
| await tester.pump(); |
| |
| expect(() => tester.allWidgets.whereType<PlatformViewSurface>().first, returnsNormally); |
| |
| expect(createdPlatformViewId, currentViewId + 1); |
| }, |
| ); |
| |
| testWidgets('PlatformViewLink Widget dispose', (WidgetTester tester) async { |
| late FakePlatformViewController disposedController; |
| final PlatformViewLink platformViewLink = PlatformViewLink( |
| viewType: 'webview', |
| 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, |
| viewType: 'webview', |
| 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: SizedBox( |
| width: 200.0, |
| height: 100.0, |
| child: createPlatformViewLink(), |
| ), |
| ), |
| ); |
| |
| expect( |
| ids, |
| unorderedEquals(<int>[ |
| currentViewId + 1, |
| ]), |
| ); |
| }); |
| |
| testWidgets('PlatformViewLink re-initializes when view type changes', (WidgetTester tester) async { |
| final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); |
| final List<int> ids = <int>[]; |
| final List<int> surfaceViewIds = <int>[]; |
| final List<String> viewTypes = <String>[]; |
| |
| PlatformViewLink createPlatformViewLink(String viewType) { |
| return PlatformViewLink( |
| viewType: viewType, |
| onCreatePlatformView: (PlatformViewCreationParams params) { |
| ids.add(params.id); |
| viewTypes.add(params.viewType); |
| controller = FakePlatformViewController(params.id); |
| params.onPlatformViewCreated(params.id); |
| return controller; |
| }, |
| surfaceFactory: (BuildContext context, PlatformViewController controller) { |
| surfaceViewIds.add(controller.viewId); |
| 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('webview'), |
| ), |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| Center( |
| child: SizedBox( |
| width: 200.0, |
| height: 100.0, |
| child: createPlatformViewLink('maps'), |
| ), |
| ), |
| ); |
| |
| expect( |
| ids, |
| unorderedEquals(<int>[ |
| currentViewId + 1, |
| currentViewId + 2, |
| ]), |
| ); |
| |
| expect( |
| surfaceViewIds, |
| unorderedEquals(<int>[ |
| currentViewId + 1, |
| currentViewId + 2, |
| ]), |
| ); |
| |
| expect( |
| viewTypes, |
| unorderedEquals(<String>[ |
| 'webview', |
| 'maps', |
| ]), |
| ); |
| }); |
| |
| testWidgets('PlatformViewLink can take any widget to return in the SurfaceFactory', (WidgetTester tester) async { |
| final PlatformViewLink platformViewLink = PlatformViewLink( |
| viewType: 'webview', |
| onCreatePlatformView: (PlatformViewCreationParams params) { |
| params.onPlatformViewCreated(params.id); |
| return FakePlatformViewController(params.id); |
| }, |
| surfaceFactory: (BuildContext context, PlatformViewController controller) { |
| return Container(); |
| }, |
| ); |
| |
| await tester.pumpWidget(platformViewLink); |
| |
| expect(() => tester.allWidgets.whereType<Container>().first, returnsNormally); |
| }); |
| |
| testWidgets('PlatformViewLink manages the focus properly', (WidgetTester tester) async { |
| final GlobalKey containerKey = GlobalKey(); |
| late FakePlatformViewController controller; |
| late ValueChanged<bool> focusChanged; |
| final PlatformViewLink platformViewLink = PlatformViewLink( |
| viewType: 'webview', |
| onCreatePlatformView: (PlatformViewCreationParams params) { |
| params.onPlatformViewCreated(params.id); |
| focusChanged = params.onFocusChanged; |
| controller = FakePlatformViewController(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: Column( |
| children: <Widget>[ |
| SizedBox(width: 300, height: 300, child: platformViewLink), |
| Focus( |
| debugLabel: 'container', |
| child: Container(key: containerKey), |
| ), |
| ], |
| ), |
| ), |
| ); |
| final Focus platformViewFocusWidget = tester.widget( |
| find.descendant( |
| of: find.byType(PlatformViewLink), |
| matching: find.byType(Focus), |
| ), |
| ); |
| final FocusNode platformViewFocusNode = platformViewFocusWidget.focusNode!; |
| final Element containerElement = tester.element(find.byKey(containerKey)); |
| final FocusNode containerFocusNode = Focus.of(containerElement); |
| |
| containerFocusNode.requestFocus(); |
| await tester.pump(); |
| |
| expect(containerFocusNode.hasFocus, true); |
| expect(platformViewFocusNode.hasFocus, false); |
| |
| // ask the platform view to gain focus |
| focusChanged(true); |
| await tester.pump(); |
| |
| expect(containerFocusNode.hasFocus, false); |
| expect(platformViewFocusNode.hasFocus, true); |
| expect(controller.focusCleared, false); |
| // ask the container to gain focus, and the platform view should clear focus. |
| containerFocusNode.requestFocus(); |
| await tester.pump(); |
| |
| expect(containerFocusNode.hasFocus, true); |
| expect(platformViewFocusNode.hasFocus, false); |
| expect(controller.focusCleared, true); |
| }); |
| |
| testWidgets('PlatformViewLink sets a platform view text input client when focused', (WidgetTester tester) async { |
| late FakePlatformViewController controller; |
| late int viewId; |
| |
| final PlatformViewLink platformViewLink = PlatformViewLink( |
| viewType: 'test', |
| onCreatePlatformView: (PlatformViewCreationParams params) { |
| viewId = params.id; |
| params.onPlatformViewCreated(params.id); |
| controller = FakePlatformViewController(params.id); |
| return controller; |
| }, |
| surfaceFactory: (BuildContext context, PlatformViewController controller) { |
| return PlatformViewSurface( |
| gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}, |
| controller: controller, |
| hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
| ); |
| }, |
| ); |
| await tester.pumpWidget(SizedBox(width: 300, height: 300, child: platformViewLink)); |
| |
| final Focus platformViewFocusWidget = tester.widget( |
| find.descendant( |
| of: find.byType(PlatformViewLink), |
| matching: find.byType(Focus), |
| ), |
| ); |
| |
| final FocusNode? focusNode = platformViewFocusWidget.focusNode; |
| expect(focusNode, isNotNull); |
| expect(focusNode!.hasFocus, false); |
| |
| late Map<String, dynamic> lastPlatformViewTextClient; |
| tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall call) { |
| if (call.method == 'TextInput.setPlatformViewClient') { |
| lastPlatformViewTextClient = call.arguments as Map<String, dynamic>; |
| } |
| return null; |
| }); |
| |
| platformViewFocusWidget.focusNode!.requestFocus(); |
| await tester.pump(); |
| |
| expect(focusNode.hasFocus, true); |
| expect(lastPlatformViewTextClient.containsKey('platformViewId'), true); |
| expect(lastPlatformViewTextClient['platformViewId'], viewId); |
| |
| expect(lastPlatformViewTextClient.containsKey('usesVirtualDisplay'), false); |
| }); |
| }); |
| |
| testWidgets('Platform views respect hitTestBehavior', (WidgetTester tester) async { |
| final FakePlatformViewController controller = FakePlatformViewController(0); |
| |
| final List<String> logs = <String>[]; |
| |
| // ------------------------- |
| // | MouseRegion1 | MouseRegion1 |
| // | |-----------------| | | |
| // | | MouseRegion2 | | |- Stack |
| // | | |---------| | | | |
| // | | |Platform | | | |- MouseRegion2 |
| // | | |View | | | |- PlatformView |
| // | | |---------| | | |
| // | | | | |
| // | |-----------------| | |
| // | | |
| // ------------------------- |
| Widget scaffold(Widget target) { |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: SizedBox( |
| width: 600, |
| height: 600, |
| child: MouseRegion( |
| onEnter: (_) { logs.add('enter1'); }, |
| onExit: (_) { logs.add('exit1'); }, |
| cursor: SystemMouseCursors.forbidden, |
| child: Stack( |
| children: <Widget>[ |
| Center( |
| child: SizedBox( |
| width: 400, |
| height: 400, |
| child: MouseRegion( |
| onEnter: (_) { logs.add('enter2'); }, |
| onExit: (_) { logs.add('exit2'); }, |
| cursor: SystemMouseCursors.text, |
| ), |
| ), |
| ), |
| Center( |
| child: SizedBox( |
| width: 200, |
| height: 200, |
| child: target, |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 0); |
| addTearDown(gesture.removePointer); |
| |
| // Test: Opaque |
| await tester.pumpWidget( |
| scaffold(PlatformViewSurface( |
| controller: controller, |
| hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
| gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}, |
| )), |
| ); |
| logs.clear(); |
| |
| await gesture.moveTo(const Offset(400, 300)); |
| expect(logs, <String>['enter1']); |
| expect(controller.dispatchedPointerEvents, hasLength(1)); |
| expect(controller.dispatchedPointerEvents[0], isA<PointerHoverEvent>()); |
| logs.clear(); |
| controller.dispatchedPointerEvents.clear(); |
| |
| // Test: changing no option does not trigger events |
| await tester.pumpWidget( |
| scaffold(PlatformViewSurface( |
| controller: controller, |
| hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
| gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}, |
| )), |
| ); |
| expect(logs, isEmpty); |
| expect(controller.dispatchedPointerEvents, isEmpty); |
| |
| // Test: Translucent |
| await tester.pumpWidget( |
| scaffold(PlatformViewSurface( |
| controller: controller, |
| hitTestBehavior: PlatformViewHitTestBehavior.translucent, |
| gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}, |
| )), |
| ); |
| expect(logs, <String>['enter2']); |
| expect(controller.dispatchedPointerEvents, isEmpty); |
| logs.clear(); |
| |
| await gesture.moveBy(const Offset(1, 1)); |
| expect(logs, isEmpty); |
| expect(controller.dispatchedPointerEvents, hasLength(1)); |
| expect(controller.dispatchedPointerEvents[0], isA<PointerHoverEvent>()); |
| expect(controller.dispatchedPointerEvents[0].position, const Offset(401, 301)); |
| expect(controller.dispatchedPointerEvents[0].localPosition, const Offset(101, 101)); |
| controller.dispatchedPointerEvents.clear(); |
| |
| // Test: Transparent |
| await tester.pumpWidget( |
| scaffold(PlatformViewSurface( |
| controller: controller, |
| hitTestBehavior: PlatformViewHitTestBehavior.transparent, |
| gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}, |
| )), |
| ); |
| expect(logs, isEmpty); |
| expect(controller.dispatchedPointerEvents, isEmpty); |
| |
| await gesture.moveBy(const Offset(1, 1)); |
| expect(logs, isEmpty); |
| expect(controller.dispatchedPointerEvents, isEmpty); |
| |
| // Test: Back to opaque |
| await tester.pumpWidget( |
| scaffold(PlatformViewSurface( |
| controller: controller, |
| hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
| gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}, |
| )), |
| ); |
| expect(logs, <String>['exit2']); |
| expect(controller.dispatchedPointerEvents, isEmpty); |
| logs.clear(); |
| |
| await gesture.moveBy(const Offset(1, 1)); |
| expect(logs, isEmpty); |
| expect(controller.dispatchedPointerEvents, hasLength(1)); |
| expect(controller.dispatchedPointerEvents[0], isA<PointerHoverEvent>()); |
| }); |
| } |