| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:typed_data'; |
| |
| import 'package:flutter/src/foundation/basic_types.dart'; |
| import 'package:flutter/src/gestures/recognizer.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:mockito/annotations.dart'; |
| import 'package:mockito/mockito.dart'; |
| import 'package:webview_flutter/webview_flutter.dart'; |
| import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; |
| |
| import 'webview_flutter_test.mocks.dart'; |
| |
| typedef VoidCallback = void Function(); |
| |
| @GenerateMocks(<Type>[WebViewPlatform, WebViewPlatformController]) |
| void main() { |
| TestWidgetsFlutterBinding.ensureInitialized(); |
| |
| late MockWebViewPlatform mockWebViewPlatform; |
| late MockWebViewPlatformController mockWebViewPlatformController; |
| late MockWebViewCookieManagerPlatform mockWebViewCookieManagerPlatform; |
| |
| setUp(() { |
| mockWebViewPlatformController = MockWebViewPlatformController(); |
| mockWebViewPlatform = MockWebViewPlatform(); |
| mockWebViewCookieManagerPlatform = MockWebViewCookieManagerPlatform(); |
| when(mockWebViewPlatform.build( |
| context: anyNamed('context'), |
| creationParams: anyNamed('creationParams'), |
| webViewPlatformCallbacksHandler: |
| anyNamed('webViewPlatformCallbacksHandler'), |
| javascriptChannelRegistry: anyNamed('javascriptChannelRegistry'), |
| onWebViewPlatformCreated: anyNamed('onWebViewPlatformCreated'), |
| gestureRecognizers: anyNamed('gestureRecognizers'), |
| )).thenAnswer((Invocation invocation) { |
| final WebViewPlatformCreatedCallback onWebViewPlatformCreated = |
| invocation.namedArguments[const Symbol('onWebViewPlatformCreated')] |
| as WebViewPlatformCreatedCallback; |
| return TestPlatformWebView( |
| mockWebViewPlatformController: mockWebViewPlatformController, |
| onWebViewPlatformCreated: onWebViewPlatformCreated, |
| ); |
| }); |
| |
| WebView.platform = mockWebViewPlatform; |
| WebViewCookieManagerPlatform.instance = mockWebViewCookieManagerPlatform; |
| }); |
| |
| tearDown(() { |
| mockWebViewCookieManagerPlatform.reset(); |
| }); |
| |
| testWidgets('Create WebView', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView()); |
| }); |
| |
| testWidgets('Initial url', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView(initialUrl: 'https://youtube.com')); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.initialUrl, 'https://youtube.com'); |
| }); |
| |
| testWidgets('Javascript mode', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView( |
| javascriptMode: JavascriptMode.unrestricted, |
| )); |
| |
| final CreationParams unrestrictedparams = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect( |
| unrestrictedparams.webSettings!.javascriptMode, |
| JavascriptMode.unrestricted, |
| ); |
| |
| await tester.pumpWidget(const WebView( |
| javascriptMode: JavascriptMode.disabled, |
| )); |
| |
| final CreationParams disabledparams = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(disabledparams.webSettings!.javascriptMode, JavascriptMode.disabled); |
| }); |
| |
| testWidgets('Load file', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| await controller!.loadFile('/test/path/index.html'); |
| |
| verify(mockWebViewPlatformController.loadFile( |
| '/test/path/index.html', |
| )); |
| }); |
| |
| testWidgets('Load file with empty path', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| expect(() => controller!.loadFile(''), throwsAssertionError); |
| }); |
| |
| testWidgets('Load Flutter asset', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| await controller!.loadFlutterAsset('assets/index.html'); |
| |
| verify(mockWebViewPlatformController.loadFlutterAsset( |
| 'assets/index.html', |
| )); |
| }); |
| |
| testWidgets('Load Flutter asset with empty key', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| expect(() => controller!.loadFlutterAsset(''), throwsAssertionError); |
| }); |
| |
| testWidgets('Load HTML string without base URL', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| await controller!.loadHtmlString('<p>This is a test paragraph.</p>'); |
| |
| verify(mockWebViewPlatformController.loadHtmlString( |
| '<p>This is a test paragraph.</p>', |
| )); |
| }); |
| |
| testWidgets('Load HTML string with base URL', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| await controller!.loadHtmlString( |
| '<p>This is a test paragraph.</p>', |
| baseUrl: 'https://flutter.dev', |
| ); |
| |
| verify(mockWebViewPlatformController.loadHtmlString( |
| '<p>This is a test paragraph.</p>', |
| baseUrl: 'https://flutter.dev', |
| )); |
| }); |
| |
| testWidgets('Load HTML string with empty string', |
| (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| expect(() => controller!.loadHtmlString(''), throwsAssertionError); |
| }); |
| |
| testWidgets('Load url', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| await controller!.loadUrl('https://flutter.io'); |
| |
| verify(mockWebViewPlatformController.loadUrl( |
| 'https://flutter.io', |
| argThat(isNull), |
| )); |
| }); |
| |
| testWidgets('Invalid urls', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.initialUrl, isNull); |
| |
| expect(() => controller!.loadUrl(''), throwsA(anything)); |
| expect(() => controller!.loadUrl('flutter.io'), throwsA(anything)); |
| }); |
| |
| testWidgets('Headers in loadUrl', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| final Map<String, String> headers = <String, String>{ |
| 'CACHE-CONTROL': 'ABC' |
| }; |
| await controller!.loadUrl('https://flutter.io', headers: headers); |
| |
| verify(mockWebViewPlatformController.loadUrl( |
| 'https://flutter.io', |
| <String, String>{'CACHE-CONTROL': 'ABC'}, |
| )); |
| }); |
| |
| testWidgets('loadRequest', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| expect(controller, isNotNull); |
| |
| final WebViewRequest req = WebViewRequest( |
| uri: Uri.parse('https://flutter.dev'), |
| method: WebViewRequestMethod.post, |
| headers: <String, String>{'foo': 'bar'}, |
| body: Uint8List.fromList('Test Body'.codeUnits), |
| ); |
| |
| await controller!.loadRequest(req); |
| |
| verify(mockWebViewPlatformController.loadRequest(req)); |
| }); |
| |
| testWidgets('Clear Cache', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| |
| await controller!.clearCache(); |
| |
| verify(mockWebViewPlatformController.clearCache()); |
| }); |
| |
| testWidgets('Can go back', (WidgetTester tester) async { |
| when(mockWebViewPlatformController.canGoBack()) |
| .thenAnswer((_) => Future<bool>.value(true)); |
| |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://flutter.io', |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| expect(controller!.canGoBack(), completion(true)); |
| }); |
| |
| testWidgets("Can't go forward", (WidgetTester tester) async { |
| when(mockWebViewPlatformController.canGoForward()) |
| .thenAnswer((_) => Future<bool>.value(false)); |
| |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| expect(controller!.canGoForward(), completion(false)); |
| }); |
| |
| testWidgets('Go back', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://youtube.com', |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| await controller!.goBack(); |
| verify(mockWebViewPlatformController.goBack()); |
| }); |
| |
| testWidgets('Go forward', (WidgetTester tester) async { |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://youtube.com', |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| await controller!.goForward(); |
| verify(mockWebViewPlatformController.goForward()); |
| }); |
| |
| testWidgets('Current URL', (WidgetTester tester) async { |
| when(mockWebViewPlatformController.currentUrl()) |
| .thenAnswer((_) => Future<String>.value('https://youtube.com')); |
| |
| WebViewController? controller; |
| await tester.pumpWidget( |
| WebView( |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect(controller, isNotNull); |
| expect(await controller!.currentUrl(), 'https://youtube.com'); |
| }); |
| |
| testWidgets('Reload url', (WidgetTester tester) async { |
| late WebViewController controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://flutter.io', |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| await controller.reload(); |
| verify(mockWebViewPlatformController.reload()); |
| }); |
| |
| testWidgets('evaluate Javascript', (WidgetTester tester) async { |
| when(mockWebViewPlatformController.evaluateJavascript('fake js string')) |
| .thenAnswer((_) => Future<String>.value('fake js string')); |
| |
| late WebViewController controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://flutter.io', |
| javascriptMode: JavascriptMode.unrestricted, |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| expect( |
| // ignore: deprecated_member_use_from_same_package |
| await controller.evaluateJavascript('fake js string'), |
| 'fake js string', |
| reason: 'should get the argument'); |
| }); |
| |
| testWidgets('evaluate Javascript with JavascriptMode disabled', |
| (WidgetTester tester) async { |
| late WebViewController controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://flutter.io', |
| javascriptMode: JavascriptMode.disabled, |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| expect( |
| // ignore: deprecated_member_use_from_same_package |
| () => controller.evaluateJavascript('fake js string'), |
| throwsA(anything), |
| ); |
| }); |
| |
| testWidgets('runJavaScript', (WidgetTester tester) async { |
| late WebViewController controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://flutter.io', |
| javascriptMode: JavascriptMode.unrestricted, |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| await controller.runJavascript('fake js string'); |
| verify(mockWebViewPlatformController.runJavascript('fake js string')); |
| }); |
| |
| testWidgets('runJavaScript with JavascriptMode disabled', |
| (WidgetTester tester) async { |
| late WebViewController controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://flutter.io', |
| javascriptMode: JavascriptMode.disabled, |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| expect( |
| () => controller.runJavascript('fake js string'), |
| throwsA(anything), |
| ); |
| }); |
| |
| testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { |
| when(mockWebViewPlatformController |
| .runJavascriptReturningResult('fake js string')) |
| .thenAnswer((_) => Future<String>.value('fake js string')); |
| |
| late WebViewController controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://flutter.io', |
| javascriptMode: JavascriptMode.unrestricted, |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| expect(await controller.runJavascriptReturningResult('fake js string'), |
| 'fake js string', |
| reason: 'should get the argument'); |
| }); |
| |
| testWidgets('runJavaScriptReturningResult with JavascriptMode disabled', |
| (WidgetTester tester) async { |
| late WebViewController controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://flutter.io', |
| javascriptMode: JavascriptMode.disabled, |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| expect( |
| () => controller.runJavascriptReturningResult('fake js string'), |
| throwsA(anything), |
| ); |
| }); |
| |
| testWidgets('Cookies can be cleared once', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const WebView( |
| initialUrl: 'https://flutter.io', |
| ), |
| ); |
| final CookieManager cookieManager = CookieManager(); |
| final bool hasCookies = await cookieManager.clearCookies(); |
| expect(hasCookies, true); |
| }); |
| |
| testWidgets('Cookies can be set', (WidgetTester tester) async { |
| const WebViewCookie cookie = |
| WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'); |
| |
| await tester.pumpWidget( |
| const WebView( |
| initialUrl: 'https://flutter.io', |
| ), |
| ); |
| final CookieManager cookieManager = CookieManager(); |
| await cookieManager.setCookie(cookie); |
| expect(mockWebViewCookieManagerPlatform.setCookieCalls, |
| <WebViewCookie>[cookie]); |
| }); |
| |
| testWidgets('Initial JavaScript channels', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://youtube.com', |
| javascriptChannels: <JavascriptChannel>{ |
| JavascriptChannel( |
| name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), |
| JavascriptChannel( |
| name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), |
| }, |
| ), |
| ); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.javascriptChannelNames, |
| unorderedEquals(<String>['Tts', 'Alarm'])); |
| }); |
| |
| test('Only valid JavaScript channel names are allowed', () { |
| void noOp(JavascriptMessage msg) {} |
| JavascriptChannel(name: 'Tts1', onMessageReceived: noOp); |
| JavascriptChannel(name: '_Alarm', onMessageReceived: noOp); |
| JavascriptChannel(name: 'foo_bar_', onMessageReceived: noOp); |
| |
| VoidCallback createChannel(String name) { |
| return () { |
| JavascriptChannel(name: name, onMessageReceived: noOp); |
| }; |
| } |
| |
| expect(createChannel('1Alarm'), throwsAssertionError); |
| expect(createChannel('foo.bar'), throwsAssertionError); |
| expect(createChannel(''), throwsAssertionError); |
| }); |
| |
| testWidgets('Unique JavaScript channel names are required', |
| (WidgetTester tester) async { |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://youtube.com', |
| javascriptChannels: <JavascriptChannel>{ |
| JavascriptChannel( |
| name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), |
| JavascriptChannel( |
| name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), |
| }, |
| ), |
| ); |
| expect(tester.takeException(), isNot(null)); |
| }); |
| |
| testWidgets('JavaScript channels update', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://youtube.com', |
| javascriptChannels: <JavascriptChannel>{ |
| JavascriptChannel( |
| name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), |
| JavascriptChannel( |
| name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), |
| }, |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://youtube.com', |
| javascriptChannels: <JavascriptChannel>{ |
| JavascriptChannel( |
| name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), |
| JavascriptChannel( |
| name: 'Alarm2', onMessageReceived: (JavascriptMessage msg) {}), |
| JavascriptChannel( |
| name: 'Alarm3', onMessageReceived: (JavascriptMessage msg) {}), |
| }, |
| ), |
| ); |
| |
| final JavascriptChannelRegistry channelRegistry = captureBuildArgs( |
| mockWebViewPlatform, |
| javascriptChannelRegistry: true, |
| ).first as JavascriptChannelRegistry; |
| |
| expect( |
| channelRegistry.channels.keys, |
| unorderedEquals(<String>['Tts', 'Alarm2', 'Alarm3']), |
| ); |
| }); |
| |
| testWidgets('Remove all JavaScript channels and then add', |
| (WidgetTester tester) async { |
| // This covers a specific bug we had where after updating javascriptChannels to null, |
| // updating it again with a subset of the previously registered channels fails as the |
| // widget's cache of current channel wasn't properly updated when updating javascriptChannels to |
| // null. |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://youtube.com', |
| javascriptChannels: <JavascriptChannel>{ |
| JavascriptChannel( |
| name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), |
| }, |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| const WebView( |
| initialUrl: 'https://youtube.com', |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://youtube.com', |
| javascriptChannels: <JavascriptChannel>{ |
| JavascriptChannel( |
| name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), |
| }, |
| ), |
| ); |
| |
| final JavascriptChannelRegistry channelRegistry = captureBuildArgs( |
| mockWebViewPlatform, |
| javascriptChannelRegistry: true, |
| ).last as JavascriptChannelRegistry; |
| |
| expect(channelRegistry.channels.keys, unorderedEquals(<String>['Tts'])); |
| }); |
| |
| testWidgets('JavaScript channel messages', (WidgetTester tester) async { |
| final List<String> ttsMessagesReceived = <String>[]; |
| final List<String> alarmMessagesReceived = <String>[]; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://youtube.com', |
| javascriptChannels: <JavascriptChannel>{ |
| JavascriptChannel( |
| name: 'Tts', |
| onMessageReceived: (JavascriptMessage msg) { |
| ttsMessagesReceived.add(msg.message); |
| }), |
| JavascriptChannel( |
| name: 'Alarm', |
| onMessageReceived: (JavascriptMessage msg) { |
| alarmMessagesReceived.add(msg.message); |
| }), |
| }, |
| ), |
| ); |
| |
| final JavascriptChannelRegistry channelRegistry = captureBuildArgs( |
| mockWebViewPlatform, |
| javascriptChannelRegistry: true, |
| ).single as JavascriptChannelRegistry; |
| |
| expect(ttsMessagesReceived, isEmpty); |
| expect(alarmMessagesReceived, isEmpty); |
| |
| channelRegistry.onJavascriptChannelMessage('Tts', 'Hello'); |
| channelRegistry.onJavascriptChannelMessage('Tts', 'World'); |
| |
| expect(ttsMessagesReceived, <String>['Hello', 'World']); |
| }); |
| |
| group('$PageStartedCallback', () { |
| testWidgets('onPageStarted is not null', (WidgetTester tester) async { |
| String? returnedUrl; |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| onPageStarted: (String url) { |
| returnedUrl = url; |
| }, |
| )); |
| |
| final WebViewPlatformCallbacksHandler handler = captureBuildArgs( |
| mockWebViewPlatform, |
| webViewPlatformCallbacksHandler: true, |
| ).single as WebViewPlatformCallbacksHandler; |
| |
| handler.onPageStarted('https://youtube.com'); |
| |
| expect(returnedUrl, 'https://youtube.com'); |
| }); |
| |
| testWidgets('onPageStarted is null', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView( |
| initialUrl: 'https://youtube.com', |
| onPageStarted: null, |
| )); |
| |
| final WebViewPlatformCallbacksHandler handler = captureBuildArgs( |
| mockWebViewPlatform, |
| webViewPlatformCallbacksHandler: true, |
| ).single as WebViewPlatformCallbacksHandler; |
| |
| // The platform side will always invoke a call for onPageStarted. This is |
| // to test that it does not crash on a null callback. |
| handler.onPageStarted('https://youtube.com'); |
| }); |
| |
| testWidgets('onPageStarted changed', (WidgetTester tester) async { |
| String? returnedUrl; |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| onPageStarted: (String url) {}, |
| )); |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| onPageStarted: (String url) { |
| returnedUrl = url; |
| }, |
| )); |
| |
| final WebViewPlatformCallbacksHandler handler = captureBuildArgs( |
| mockWebViewPlatform, |
| webViewPlatformCallbacksHandler: true, |
| ).last as WebViewPlatformCallbacksHandler; |
| handler.onPageStarted('https://youtube.com'); |
| |
| expect(returnedUrl, 'https://youtube.com'); |
| }); |
| }); |
| |
| group('$PageFinishedCallback', () { |
| testWidgets('onPageFinished is not null', (WidgetTester tester) async { |
| String? returnedUrl; |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| onPageFinished: (String url) { |
| returnedUrl = url; |
| }, |
| )); |
| |
| final WebViewPlatformCallbacksHandler handler = captureBuildArgs( |
| mockWebViewPlatform, |
| webViewPlatformCallbacksHandler: true, |
| ).single as WebViewPlatformCallbacksHandler; |
| handler.onPageFinished('https://youtube.com'); |
| |
| expect(returnedUrl, 'https://youtube.com'); |
| }); |
| |
| testWidgets('onPageFinished is null', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView( |
| initialUrl: 'https://youtube.com', |
| onPageFinished: null, |
| )); |
| |
| final WebViewPlatformCallbacksHandler handler = captureBuildArgs( |
| mockWebViewPlatform, |
| webViewPlatformCallbacksHandler: true, |
| ).single as WebViewPlatformCallbacksHandler; |
| // The platform side will always invoke a call for onPageFinished. This is |
| // to test that it does not crash on a null callback. |
| handler.onPageFinished('https://youtube.com'); |
| }); |
| |
| testWidgets('onPageFinished changed', (WidgetTester tester) async { |
| String? returnedUrl; |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| onPageFinished: (String url) {}, |
| )); |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| onPageFinished: (String url) { |
| returnedUrl = url; |
| }, |
| )); |
| |
| final WebViewPlatformCallbacksHandler handler = captureBuildArgs( |
| mockWebViewPlatform, |
| webViewPlatformCallbacksHandler: true, |
| ).last as WebViewPlatformCallbacksHandler; |
| handler.onPageFinished('https://youtube.com'); |
| |
| expect(returnedUrl, 'https://youtube.com'); |
| }); |
| }); |
| |
| group('$PageLoadingCallback', () { |
| testWidgets('onLoadingProgress is not null', (WidgetTester tester) async { |
| int? loadingProgress; |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| onProgress: (int progress) { |
| loadingProgress = progress; |
| }, |
| )); |
| |
| final WebViewPlatformCallbacksHandler handler = captureBuildArgs( |
| mockWebViewPlatform, |
| webViewPlatformCallbacksHandler: true, |
| ).single as WebViewPlatformCallbacksHandler; |
| handler.onProgress(50); |
| |
| expect(loadingProgress, 50); |
| }); |
| |
| testWidgets('onLoadingProgress is null', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView( |
| initialUrl: 'https://youtube.com', |
| onProgress: null, |
| )); |
| |
| final WebViewPlatformCallbacksHandler handler = captureBuildArgs( |
| mockWebViewPlatform, |
| webViewPlatformCallbacksHandler: true, |
| ).single as WebViewPlatformCallbacksHandler; |
| |
| // This is to test that it does not crash on a null callback. |
| handler.onProgress(50); |
| }); |
| |
| testWidgets('onLoadingProgress changed', (WidgetTester tester) async { |
| int? loadingProgress; |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| onProgress: (int progress) {}, |
| )); |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| onProgress: (int progress) { |
| loadingProgress = progress; |
| }, |
| )); |
| |
| final WebViewPlatformCallbacksHandler handler = captureBuildArgs( |
| mockWebViewPlatform, |
| webViewPlatformCallbacksHandler: true, |
| ).last as WebViewPlatformCallbacksHandler; |
| handler.onProgress(50); |
| |
| expect(loadingProgress, 50); |
| }); |
| }); |
| |
| group('navigationDelegate', () { |
| testWidgets('hasNavigationDelegate', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView( |
| initialUrl: 'https://youtube.com', |
| )); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.webSettings!.hasNavigationDelegate, false); |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| navigationDelegate: (NavigationRequest r) => |
| NavigationDecision.navigate, |
| )); |
| |
| final WebSettings updateSettings = |
| verify(mockWebViewPlatformController.updateSettings(captureAny)) |
| .captured |
| .single as WebSettings; |
| |
| expect(updateSettings.hasNavigationDelegate, true); |
| }); |
| |
| testWidgets('Block navigation', (WidgetTester tester) async { |
| final List<NavigationRequest> navigationRequests = <NavigationRequest>[]; |
| |
| await tester.pumpWidget(WebView( |
| initialUrl: 'https://youtube.com', |
| navigationDelegate: (NavigationRequest request) { |
| navigationRequests.add(request); |
| // Only allow navigating to https://flutter.dev |
| return request.url == 'https://flutter.dev' |
| ? NavigationDecision.navigate |
| : NavigationDecision.prevent; |
| })); |
| |
| final List<dynamic> args = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| webViewPlatformCallbacksHandler: true, |
| ); |
| |
| final CreationParams params = args[0] as CreationParams; |
| expect(params.webSettings!.hasNavigationDelegate, true); |
| |
| final WebViewPlatformCallbacksHandler handler = |
| args[1] as WebViewPlatformCallbacksHandler; |
| |
| // The navigation delegate only allows navigation to https://flutter.dev |
| // so we should still be in https://youtube.com. |
| expect( |
| handler.onNavigationRequest( |
| url: 'https://www.google.com', |
| isForMainFrame: true, |
| ), |
| completion(false), |
| ); |
| |
| expect(navigationRequests.length, 1); |
| expect(navigationRequests[0].url, 'https://www.google.com'); |
| expect(navigationRequests[0].isForMainFrame, true); |
| |
| expect( |
| handler.onNavigationRequest( |
| url: 'https://flutter.dev', |
| isForMainFrame: true, |
| ), |
| completion(true), |
| ); |
| }); |
| }); |
| |
| group('debuggingEnabled', () { |
| testWidgets('enable debugging', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView( |
| debuggingEnabled: true, |
| )); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.webSettings!.debuggingEnabled, true); |
| }); |
| |
| testWidgets('defaults to false', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView()); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.webSettings!.debuggingEnabled, false); |
| }); |
| |
| testWidgets('can be changed', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(WebView(key: key)); |
| |
| await tester.pumpWidget(WebView( |
| key: key, |
| debuggingEnabled: true, |
| )); |
| |
| final WebSettings enabledSettings = |
| verify(mockWebViewPlatformController.updateSettings(captureAny)) |
| .captured |
| .last as WebSettings; |
| expect(enabledSettings.debuggingEnabled, true); |
| |
| await tester.pumpWidget(WebView( |
| key: key, |
| debuggingEnabled: false, |
| )); |
| |
| final WebSettings disabledSettings = |
| verify(mockWebViewPlatformController.updateSettings(captureAny)) |
| .captured |
| .last as WebSettings; |
| expect(disabledSettings.debuggingEnabled, false); |
| }); |
| }); |
| |
| group('zoomEnabled', () { |
| testWidgets('Enable zoom', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView( |
| zoomEnabled: true, |
| )); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.webSettings!.zoomEnabled, isTrue); |
| }); |
| |
| testWidgets('defaults to true', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView()); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.webSettings!.zoomEnabled, isTrue); |
| }); |
| |
| testWidgets('can be changed', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(WebView(key: key)); |
| |
| await tester.pumpWidget(WebView( |
| key: key, |
| zoomEnabled: true, |
| )); |
| |
| final WebSettings enabledSettings = |
| verify(mockWebViewPlatformController.updateSettings(captureAny)) |
| .captured |
| .last as WebSettings; |
| // Zoom defaults to true, so no changes are made to settings. |
| expect(enabledSettings.zoomEnabled, isNull); |
| |
| await tester.pumpWidget(WebView( |
| key: key, |
| zoomEnabled: false, |
| )); |
| |
| final WebSettings disabledSettings = |
| verify(mockWebViewPlatformController.updateSettings(captureAny)) |
| .captured |
| .last as WebSettings; |
| expect(disabledSettings.zoomEnabled, isFalse); |
| }); |
| }); |
| |
| group('Background color', () { |
| testWidgets('Defaults to null', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView()); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.backgroundColor, null); |
| }); |
| |
| testWidgets('Can be transparent', (WidgetTester tester) async { |
| const Color transparentColor = Color(0x00000000); |
| |
| await tester.pumpWidget(const WebView( |
| backgroundColor: transparentColor, |
| )); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.backgroundColor, transparentColor); |
| }); |
| }); |
| |
| group('Custom platform implementation', () { |
| setUp(() { |
| WebView.platform = MyWebViewPlatform(); |
| }); |
| tearDownAll(() { |
| WebView.platform = null; |
| }); |
| |
| testWidgets('creation', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const WebView( |
| initialUrl: 'https://youtube.com', |
| gestureNavigationEnabled: true, |
| ), |
| ); |
| |
| final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; |
| final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; |
| |
| expect( |
| platform.creationParams, |
| MatchesCreationParams(CreationParams( |
| initialUrl: 'https://youtube.com', |
| webSettings: WebSettings( |
| javascriptMode: JavascriptMode.disabled, |
| hasNavigationDelegate: false, |
| debuggingEnabled: false, |
| userAgent: const WebSetting<String?>.of(null), |
| gestureNavigationEnabled: true, |
| zoomEnabled: true, |
| ), |
| ))); |
| }); |
| |
| testWidgets('loadUrl', (WidgetTester tester) async { |
| late WebViewController controller; |
| await tester.pumpWidget( |
| WebView( |
| initialUrl: 'https://youtube.com', |
| onWebViewCreated: (WebViewController webViewController) { |
| controller = webViewController; |
| }, |
| ), |
| ); |
| |
| final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; |
| final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; |
| |
| final Map<String, String> headers = <String, String>{ |
| 'header': 'value', |
| }; |
| |
| await controller.loadUrl('https://google.com', headers: headers); |
| |
| expect(platform.lastUrlLoaded, 'https://google.com'); |
| expect(platform.lastRequestHeaders, headers); |
| }); |
| }); |
| |
| testWidgets('Set UserAgent', (WidgetTester tester) async { |
| await tester.pumpWidget(const WebView( |
| initialUrl: 'https://youtube.com', |
| javascriptMode: JavascriptMode.unrestricted, |
| )); |
| |
| final CreationParams params = captureBuildArgs( |
| mockWebViewPlatform, |
| creationParams: true, |
| ).single as CreationParams; |
| |
| expect(params.webSettings!.userAgent.value, isNull); |
| |
| await tester.pumpWidget(const WebView( |
| initialUrl: 'https://youtube.com', |
| javascriptMode: JavascriptMode.unrestricted, |
| userAgent: 'UA', |
| )); |
| |
| final WebSettings settings = |
| verify(mockWebViewPlatformController.updateSettings(captureAny)) |
| .captured |
| .last as WebSettings; |
| expect(settings.userAgent.value, 'UA'); |
| }); |
| } |
| |
| List<dynamic> captureBuildArgs( |
| MockWebViewPlatform mockWebViewPlatform, { |
| bool context = false, |
| bool creationParams = false, |
| bool webViewPlatformCallbacksHandler = false, |
| bool javascriptChannelRegistry = false, |
| bool onWebViewPlatformCreated = false, |
| bool gestureRecognizers = false, |
| }) { |
| return verify(mockWebViewPlatform.build( |
| context: context ? captureAnyNamed('context') : anyNamed('context'), |
| creationParams: creationParams |
| ? captureAnyNamed('creationParams') |
| : anyNamed('creationParams'), |
| webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler |
| ? captureAnyNamed('webViewPlatformCallbacksHandler') |
| : anyNamed('webViewPlatformCallbacksHandler'), |
| javascriptChannelRegistry: javascriptChannelRegistry |
| ? captureAnyNamed('javascriptChannelRegistry') |
| : anyNamed('javascriptChannelRegistry'), |
| onWebViewPlatformCreated: onWebViewPlatformCreated |
| ? captureAnyNamed('onWebViewPlatformCreated') |
| : anyNamed('onWebViewPlatformCreated'), |
| gestureRecognizers: gestureRecognizers |
| ? captureAnyNamed('gestureRecognizers') |
| : anyNamed('gestureRecognizers'), |
| )).captured; |
| } |
| |
| // This Widget ensures that onWebViewPlatformCreated is only called once when |
| // making multiple calls to `WidgetTester.pumpWidget` with different parameters |
| // for the WebView. |
| class TestPlatformWebView extends StatefulWidget { |
| const TestPlatformWebView({ |
| Key? key, |
| required this.mockWebViewPlatformController, |
| this.onWebViewPlatformCreated, |
| }) : super(key: key); |
| |
| final MockWebViewPlatformController mockWebViewPlatformController; |
| final WebViewPlatformCreatedCallback? onWebViewPlatformCreated; |
| |
| @override |
| State<StatefulWidget> createState() => TestPlatformWebViewState(); |
| } |
| |
| class TestPlatformWebViewState extends State<TestPlatformWebView> { |
| @override |
| void initState() { |
| super.initState(); |
| final WebViewPlatformCreatedCallback? onWebViewPlatformCreated = |
| widget.onWebViewPlatformCreated; |
| if (onWebViewPlatformCreated != null) { |
| onWebViewPlatformCreated(widget.mockWebViewPlatformController); |
| } |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Container(); |
| } |
| } |
| |
| class MyWebViewPlatform implements WebViewPlatform { |
| MyWebViewPlatformController? lastPlatformBuilt; |
| |
| @override |
| Widget build({ |
| BuildContext? context, |
| CreationParams? creationParams, |
| required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, |
| required JavascriptChannelRegistry javascriptChannelRegistry, |
| WebViewPlatformCreatedCallback? onWebViewPlatformCreated, |
| Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers, |
| }) { |
| assert(onWebViewPlatformCreated != null); |
| lastPlatformBuilt = MyWebViewPlatformController( |
| creationParams, gestureRecognizers, webViewPlatformCallbacksHandler); |
| onWebViewPlatformCreated!(lastPlatformBuilt); |
| return Container(); |
| } |
| |
| @override |
| Future<bool> clearCookies() { |
| return Future<bool>.sync(() => true); |
| } |
| } |
| |
| class MyWebViewPlatformController extends WebViewPlatformController { |
| MyWebViewPlatformController(this.creationParams, this.gestureRecognizers, |
| WebViewPlatformCallbacksHandler platformHandler) |
| : super(platformHandler); |
| |
| CreationParams? creationParams; |
| Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers; |
| |
| String? lastUrlLoaded; |
| Map<String, String>? lastRequestHeaders; |
| |
| @override |
| Future<void> loadUrl(String url, Map<String, String>? headers) async { |
| equals(1, 1); |
| lastUrlLoaded = url; |
| lastRequestHeaders = headers; |
| } |
| } |
| |
| class MatchesWebSettings extends Matcher { |
| MatchesWebSettings(this._webSettings); |
| |
| final WebSettings? _webSettings; |
| |
| @override |
| Description describe(Description description) => |
| description.add('$_webSettings'); |
| |
| @override |
| bool matches( |
| covariant WebSettings webSettings, Map<dynamic, dynamic> matchState) { |
| return _webSettings!.javascriptMode == webSettings.javascriptMode && |
| _webSettings!.hasNavigationDelegate == |
| webSettings.hasNavigationDelegate && |
| _webSettings!.debuggingEnabled == webSettings.debuggingEnabled && |
| _webSettings!.gestureNavigationEnabled == |
| webSettings.gestureNavigationEnabled && |
| _webSettings!.userAgent == webSettings.userAgent && |
| _webSettings!.zoomEnabled == webSettings.zoomEnabled; |
| } |
| } |
| |
| class MatchesCreationParams extends Matcher { |
| MatchesCreationParams(this._creationParams); |
| |
| final CreationParams _creationParams; |
| |
| @override |
| Description describe(Description description) => |
| description.add('$_creationParams'); |
| |
| @override |
| bool matches(covariant CreationParams creationParams, |
| Map<dynamic, dynamic> matchState) { |
| return _creationParams.initialUrl == creationParams.initialUrl && |
| MatchesWebSettings(_creationParams.webSettings) |
| .matches(creationParams.webSettings!, matchState) && |
| orderedEquals(_creationParams.javascriptChannelNames) |
| .matches(creationParams.javascriptChannelNames, matchState); |
| } |
| } |
| |
| class MockWebViewCookieManagerPlatform extends WebViewCookieManagerPlatform { |
| List<WebViewCookie> setCookieCalls = <WebViewCookie>[]; |
| |
| @override |
| Future<bool> clearCookies() async => true; |
| |
| @override |
| Future<void> setCookie(WebViewCookie cookie) async { |
| setCookieCalls.add(cookie); |
| } |
| |
| void reset() { |
| setCookieCalls = <WebViewCookie>[]; |
| } |
| } |