| // 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:async'; |
| |
| 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_platform_interface/webview_flutter_platform_interface.dart'; |
| import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; |
| import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; |
| import 'package:webview_flutter_wkwebview/src/web_kit_webview_widget.dart'; |
| |
| import 'web_kit_webview_widget_test.mocks.dart'; |
| |
| @GenerateMocks(<Type>[ |
| WKNavigationDelegate, |
| WKScriptMessageHandler, |
| WKWebView, |
| WKWebViewConfiguration, |
| WKWebsiteDataStore, |
| WKUIDelegate, |
| WKUserContentController, |
| JavascriptChannelRegistry, |
| WebViewPlatformCallbacksHandler, |
| WebViewWidgetProxy, |
| ]) |
| void main() { |
| TestWidgetsFlutterBinding.ensureInitialized(); |
| |
| group('$WebKitWebViewWidget', () { |
| late MockWKWebView mockWebView; |
| late MockWebViewWidgetProxy mockWebViewWidgetProxy; |
| late MockWKUserContentController mockUserContentController; |
| late MockWKWebViewConfiguration mockWebViewConfiguration; |
| late MockWKUIDelegate mockUIDelegate; |
| late MockWKWebsiteDataStore mockWebsiteDataStore; |
| late MockWKNavigationDelegate mockNavigationDelegate; |
| |
| late MockWebViewPlatformCallbacksHandler mockCallbacksHandler; |
| late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; |
| |
| late WebKitWebViewPlatformController testController; |
| |
| setUp(() { |
| mockWebView = MockWKWebView(); |
| mockWebViewConfiguration = MockWKWebViewConfiguration(); |
| mockUserContentController = MockWKUserContentController(); |
| mockUIDelegate = MockWKUIDelegate(); |
| mockWebsiteDataStore = MockWKWebsiteDataStore(); |
| mockNavigationDelegate = MockWKNavigationDelegate(); |
| mockWebViewWidgetProxy = MockWebViewWidgetProxy(); |
| |
| when(mockWebViewWidgetProxy.createWebView(any)).thenReturn(mockWebView); |
| when(mockWebViewWidgetProxy.createUIDelgate()).thenReturn(mockUIDelegate); |
| when(mockWebViewWidgetProxy.createNavigationDelegate()) |
| .thenReturn(mockNavigationDelegate); |
| when(mockWebView.configuration).thenReturn(mockWebViewConfiguration); |
| when(mockWebViewConfiguration.userContentController).thenReturn( |
| mockUserContentController, |
| ); |
| when(mockWebViewConfiguration.webSiteDataStore).thenReturn( |
| mockWebsiteDataStore, |
| ); |
| |
| mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); |
| mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); |
| }); |
| |
| // Builds a WebViewCupertinoWidget with default parameters. |
| Future<void> buildWidget( |
| WidgetTester tester, { |
| CreationParams? creationParams, |
| bool hasNavigationDelegate = false, |
| bool hasProgressTracking = false, |
| }) async { |
| await tester.pumpWidget(WebKitWebViewWidget( |
| creationParams: creationParams ?? |
| CreationParams( |
| webSettings: WebSettings( |
| userAgent: const WebSetting<String?>.absent(), |
| hasNavigationDelegate: hasNavigationDelegate, |
| hasProgressTracking: hasProgressTracking, |
| )), |
| callbacksHandler: mockCallbacksHandler, |
| javascriptChannelRegistry: mockJavascriptChannelRegistry, |
| webViewProxy: mockWebViewWidgetProxy, |
| configuration: mockWebViewConfiguration, |
| onBuildWidget: (WebKitWebViewPlatformController controller) { |
| testController = controller; |
| return Container(); |
| }, |
| )); |
| await tester.pumpAndSettle(); |
| } |
| |
| testWidgets('build $WebKitWebViewWidget', (WidgetTester tester) async { |
| await buildWidget(tester); |
| }); |
| |
| testWidgets('Requests to open a new window loads request in same window', |
| (WidgetTester tester) async { |
| await buildWidget(tester); |
| |
| final dynamic onCreateWebView = |
| verify(mockUIDelegate.onCreateWebView = captureAny).captured.single |
| as void Function(WKWebViewConfiguration, WKNavigationAction); |
| |
| const NSUrlRequest request = NSUrlRequest(url: 'https://google.com'); |
| onCreateWebView( |
| mockWebViewConfiguration, |
| const WKNavigationAction( |
| request: request, |
| targetFrame: WKFrameInfo(isMainFrame: false), |
| ), |
| ); |
| |
| verify(mockWebView.loadRequest(request)); |
| }); |
| |
| group('$CreationParams', () { |
| testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async { |
| await buildWidget( |
| tester, |
| creationParams: CreationParams( |
| autoMediaPlaybackPolicy: |
| AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, |
| webSettings: WebSettings( |
| userAgent: const WebSetting<String?>.absent(), |
| hasNavigationDelegate: false, |
| ), |
| ), |
| ); |
| |
| verify( |
| mockWebViewConfiguration.mediaTypesRequiringUserActionForPlayback = |
| <WKAudiovisualMediaType>{ |
| WKAudiovisualMediaType.all, |
| }); |
| }); |
| |
| testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async { |
| await buildWidget( |
| tester, |
| creationParams: CreationParams( |
| autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, |
| webSettings: WebSettings( |
| userAgent: const WebSetting<String?>.absent(), |
| hasNavigationDelegate: false, |
| ), |
| ), |
| ); |
| |
| verify( |
| mockWebViewConfiguration.mediaTypesRequiringUserActionForPlayback = |
| <WKAudiovisualMediaType>{ |
| WKAudiovisualMediaType.none, |
| }); |
| }); |
| |
| testWidgets('javascriptChannelNames', (WidgetTester tester) async { |
| when(mockWebViewWidgetProxy.createScriptMessageHandler()).thenReturn( |
| MockWKScriptMessageHandler(), |
| ); |
| |
| await buildWidget( |
| tester, |
| creationParams: CreationParams( |
| javascriptChannelNames: <String>{'a', 'b'}, |
| webSettings: WebSettings( |
| userAgent: const WebSetting<String?>.absent(), |
| hasNavigationDelegate: false, |
| ), |
| ), |
| ); |
| |
| final List<dynamic> javaScriptChannels = verify( |
| mockUserContentController.addScriptMessageHandler( |
| captureAny, |
| captureAny, |
| ), |
| ).captured; |
| expect( |
| javaScriptChannels[0], |
| isA<WKScriptMessageHandler>(), |
| ); |
| expect(javaScriptChannels[1], 'a'); |
| expect( |
| javaScriptChannels[2], |
| isA<WKScriptMessageHandler>(), |
| ); |
| expect(javaScriptChannels[3], 'b'); |
| }); |
| |
| group('$WebSettings', () { |
| testWidgets('allowsInlineMediaPlayback', (WidgetTester tester) async { |
| await buildWidget( |
| tester, |
| creationParams: CreationParams( |
| webSettings: WebSettings( |
| userAgent: const WebSetting<String?>.absent(), |
| allowsInlineMediaPlayback: true, |
| ), |
| ), |
| ); |
| |
| verify(mockWebViewConfiguration.allowsInlineMediaPlayback = true); |
| }); |
| }); |
| }); |
| |
| group('$WebKitWebViewPlatformController', () { |
| testWidgets('clearCache', (WidgetTester tester) async { |
| await buildWidget(tester); |
| |
| await testController.clearCache(); |
| verify(mockWebsiteDataStore.removeDataOfTypes( |
| <WKWebsiteDataTypes>{ |
| WKWebsiteDataTypes.memoryCache, |
| WKWebsiteDataTypes.diskCache, |
| WKWebsiteDataTypes.offlineWebApplicationCache, |
| WKWebsiteDataTypes.localStroage, |
| }, |
| DateTime.fromMillisecondsSinceEpoch(0), |
| )); |
| }); |
| |
| testWidgets('addJavascriptChannels', (WidgetTester tester) async { |
| when(mockWebViewWidgetProxy.createScriptMessageHandler()).thenReturn( |
| MockWKScriptMessageHandler(), |
| ); |
| |
| await buildWidget(tester); |
| |
| await testController.addJavascriptChannels(<String>{'c', 'd'}); |
| final List<dynamic> javaScriptChannels = verify( |
| mockUserContentController.addScriptMessageHandler( |
| captureAny, captureAny), |
| ).captured; |
| expect( |
| javaScriptChannels[0], |
| isA<WKScriptMessageHandler>(), |
| ); |
| expect(javaScriptChannels[1], 'c'); |
| expect( |
| javaScriptChannels[2], |
| isA<WKScriptMessageHandler>(), |
| ); |
| expect(javaScriptChannels[3], 'd'); |
| |
| final List<WKUserScript> userScripts = |
| verify(mockUserContentController.addUserScript(captureAny)) |
| .captured |
| .cast<WKUserScript>(); |
| expect(userScripts[0].source, 'window.c = webkit.messageHandlers.c;'); |
| expect( |
| userScripts[0].injectionTime, |
| WKUserScriptInjectionTime.atDocumentStart, |
| ); |
| expect(userScripts[0].isMainFrameOnly, false); |
| expect(userScripts[1].source, 'window.d = webkit.messageHandlers.d;'); |
| expect( |
| userScripts[1].injectionTime, |
| WKUserScriptInjectionTime.atDocumentStart, |
| ); |
| expect(userScripts[0].isMainFrameOnly, false); |
| }); |
| |
| testWidgets('removeJavascriptChannels', (WidgetTester tester) async { |
| when(mockWebViewWidgetProxy.createScriptMessageHandler()).thenReturn( |
| MockWKScriptMessageHandler(), |
| ); |
| |
| await buildWidget(tester); |
| |
| await testController.addJavascriptChannels(<String>{'c', 'd'}); |
| reset(mockUserContentController); |
| |
| await testController.removeJavascriptChannels(<String>{'c'}); |
| |
| verify(mockUserContentController.removeAllScriptMessageHandlers()); |
| verify(mockUserContentController.removeAllUserScripts()); |
| |
| final List<dynamic> javaScriptChannels = verify( |
| mockUserContentController.addScriptMessageHandler( |
| captureAny, captureAny), |
| ).captured; |
| expect( |
| javaScriptChannels[0], |
| isA<WKScriptMessageHandler>(), |
| ); |
| expect(javaScriptChannels[1], 'd'); |
| |
| final List<WKUserScript> userScripts = |
| verify(mockUserContentController.addUserScript(captureAny)) |
| .captured |
| .cast<WKUserScript>(); |
| expect(userScripts[0].source, 'window.d = webkit.messageHandlers.d;'); |
| expect( |
| userScripts[0].injectionTime, |
| WKUserScriptInjectionTime.atDocumentStart, |
| ); |
| expect(userScripts[0].isMainFrameOnly, false); |
| }); |
| }); |
| |
| group('$WebViewPlatformCallbacksHandler', () { |
| testWidgets('onPageStarted', (WidgetTester tester) async { |
| await buildWidget(tester); |
| |
| final dynamic didStartProvisionalNavigation = verify( |
| mockNavigationDelegate.didStartProvisionalNavigation = |
| captureAny) |
| .captured |
| .single as void Function(WKWebView, String); |
| didStartProvisionalNavigation(mockWebView, 'https://google.com'); |
| |
| verify(mockCallbacksHandler.onPageStarted('https://google.com')); |
| }); |
| |
| testWidgets('onPageFinished', (WidgetTester tester) async { |
| await buildWidget(tester); |
| |
| final dynamic didFinishNavigation = |
| verify(mockNavigationDelegate.didFinishNavigation = captureAny) |
| .captured |
| .single as void Function(WKWebView, String); |
| didFinishNavigation(mockWebView, 'https://google.com'); |
| |
| verify(mockCallbacksHandler.onPageFinished('https://google.com')); |
| }); |
| |
| testWidgets('onWebResourceError from didFailNavigation', |
| (WidgetTester tester) async { |
| await buildWidget(tester); |
| |
| final dynamic didFailNavigation = |
| verify(mockNavigationDelegate.didFailNavigation = captureAny) |
| .captured |
| .single as void Function(WKWebView, NSError); |
| |
| didFailNavigation( |
| mockWebView, |
| const NSError( |
| code: WKErrorCode.webViewInvalidated, |
| domain: 'domain', |
| localizedDescription: 'my desc', |
| ), |
| ); |
| |
| final WebResourceError error = |
| verify(mockCallbacksHandler.onWebResourceError(captureAny)) |
| .captured |
| .single as WebResourceError; |
| expect(error.description, 'my desc'); |
| expect(error.errorCode, WKErrorCode.webViewInvalidated); |
| expect(error.domain, 'domain'); |
| expect(error.errorType, WebResourceErrorType.webViewInvalidated); |
| }); |
| |
| testWidgets('onWebResourceError from didFailProvisionalNavigation', |
| (WidgetTester tester) async { |
| await buildWidget(tester); |
| |
| final dynamic didFailProvisionalNavigation = verify( |
| mockNavigationDelegate.didFailProvisionalNavigation = |
| captureAny) |
| .captured |
| .single as void Function(WKWebView, NSError); |
| |
| didFailProvisionalNavigation( |
| mockWebView, |
| const NSError( |
| code: WKErrorCode.webContentProcessTerminated, |
| domain: 'domain', |
| localizedDescription: 'my desc', |
| ), |
| ); |
| |
| final WebResourceError error = |
| verify(mockCallbacksHandler.onWebResourceError(captureAny)) |
| .captured |
| .single as WebResourceError; |
| expect(error.description, 'my desc'); |
| expect(error.errorCode, WKErrorCode.webContentProcessTerminated); |
| expect(error.domain, 'domain'); |
| expect( |
| error.errorType, |
| WebResourceErrorType.webContentProcessTerminated, |
| ); |
| }); |
| |
| testWidgets( |
| 'onWebResourceError from webViewWebContentProcessDidTerminate', |
| (WidgetTester tester) async { |
| await buildWidget(tester); |
| |
| final dynamic webViewWebContentProcessDidTerminate = verify( |
| mockNavigationDelegate.webViewWebContentProcessDidTerminate = |
| captureAny) |
| .captured |
| .single as void Function(WKWebView); |
| webViewWebContentProcessDidTerminate(mockWebView); |
| |
| final WebResourceError error = |
| verify(mockCallbacksHandler.onWebResourceError(captureAny)) |
| .captured |
| .single as WebResourceError; |
| expect(error.description, ''); |
| expect(error.errorCode, WKErrorCode.webContentProcessTerminated); |
| expect(error.domain, 'WKErrorDomain'); |
| expect( |
| error.errorType, |
| WebResourceErrorType.webContentProcessTerminated, |
| ); |
| }); |
| |
| testWidgets('onNavigationRequest from decidePolicyForNavigationAction', |
| (WidgetTester tester) async { |
| await buildWidget(tester, hasNavigationDelegate: true); |
| |
| final dynamic decidePolicyForNavigationAction = verify( |
| mockNavigationDelegate.decidePolicyForNavigationAction = |
| captureAny) |
| .captured |
| .single |
| as Future<WKNavigationActionPolicy> Function( |
| WKWebView, WKNavigationAction); |
| |
| when(mockCallbacksHandler.onNavigationRequest( |
| isForMainFrame: argThat(isFalse, named: 'isForMainFrame'), |
| url: 'https://google.com', |
| )).thenReturn(true); |
| |
| expect( |
| decidePolicyForNavigationAction( |
| mockWebView, |
| const WKNavigationAction( |
| request: NSUrlRequest(url: 'https://google.com'), |
| targetFrame: WKFrameInfo(isMainFrame: false), |
| ), |
| ), |
| completion(WKNavigationActionPolicy.allow), |
| ); |
| |
| verify(mockCallbacksHandler.onNavigationRequest( |
| url: 'https://google.com', |
| isForMainFrame: false, |
| )); |
| }); |
| }); |
| |
| group('$JavascriptChannelRegistry', () { |
| testWidgets('onJavascriptChannelMessage', (WidgetTester tester) async { |
| when(mockWebViewWidgetProxy.createScriptMessageHandler()).thenReturn( |
| MockWKScriptMessageHandler(), |
| ); |
| |
| await buildWidget(tester); |
| await testController.addJavascriptChannels(<String>{'hello'}); |
| |
| final MockWKScriptMessageHandler messageHandler = verify( |
| mockUserContentController.addScriptMessageHandler( |
| captureAny, 'hello')) |
| .captured |
| .single as MockWKScriptMessageHandler; |
| |
| final dynamic didReceiveScriptMessage = |
| verify(messageHandler.didReceiveScriptMessage = captureAny) |
| .captured |
| .single as void Function( |
| WKUserContentController userContentController, |
| WKScriptMessage message, |
| ); |
| |
| didReceiveScriptMessage( |
| mockUserContentController, |
| const WKScriptMessage(name: 'hello', body: 'A message.'), |
| ); |
| verify(mockJavascriptChannelRegistry.onJavascriptChannelMessage( |
| 'hello', |
| 'A message.', |
| )); |
| }); |
| }); |
| }); |
| } |