blob: b10366c820245a4b9603b26bfca50cd090b1265c [file] [log] [blame]
// 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());
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',
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',
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',
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',
));
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',
));
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',
));
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,
));
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());
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,
));
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>[];
}
}