[url_launcher_web] Migrate to null-safety (#3522)

This version uses auto-generated Mocks from mockito5 for its tests.

For now, tests only run in the `master` channel.

Co-authored-by: Maurits van Beusekom <maurits@baseflow.com>
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 47cdbd2..2b62a6a 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -4,6 +4,7 @@
     # Ignore generated files
     - '**/*.g.dart'
     - 'lib/src/generated/*.dart'
+    - '**/*.mocks.dart' # Mockito @GenerateMocks
   errors:
     always_require_non_null_named_parameters: false # not needed with nnbd
     unnecessary_null_comparison: false # Turned as long as nnbd mix-mode is supported.
diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md
index 0416c03..49d7245 100644
--- a/packages/url_launcher/url_launcher_web/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 2.0.0-nullsafety
+
+- Migrate to null safety.
+
 # 0.1.5+3
 
 - Fix Link misalignment [issue](https://github.com/flutter/flutter/issues/70053).
diff --git a/packages/url_launcher/url_launcher_web/example/README.md b/packages/url_launcher/url_launcher_web/example/README.md
new file mode 100644
index 0000000..b75df09
--- /dev/null
+++ b/packages/url_launcher/url_launcher_web/example/README.md
@@ -0,0 +1,31 @@
+# Testing
+
+This package utilizes the `integration_test` package to run its tests in a web browser.
+
+See [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info.
+
+## Running the tests
+
+Make sure you have updated to the latest Flutter master.
+
+1. Check what version of Chrome is running on the machine you're running tests on.
+
+2. Download and install driver for that version from here:
+    * <https://chromedriver.chromium.org/downloads>
+
+3. Start the driver using `chromedriver --port=4444`
+
+4. Run tests: `flutter drive -d web-server --browser-name=chrome --driver=test_driver/integration_test_driver.dart --target=integration_test/TEST_NAME.dart`, or (in Linux):
+
+    * Single: `./run_test.sh integration_test/TEST_NAME.dart`
+    * All: `./run_test.sh`
+
+## Mocks
+
+There's `.mocks.dart` files next to the test files that use them.
+
+They're [generated by Mockito](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#code-generation).
+
+Mocks might be manually re-generated with the following command: `flutter pub run build_runner build`. If there are any changes in the mocks, feel free to commit them.
+
+(Mocks will be auto-generated by the `run_test.sh` script as well.)
diff --git a/packages/url_launcher/url_launcher_web/example/build.yaml b/packages/url_launcher/url_launcher_web/example/build.yaml
new file mode 100644
index 0000000..db3104b
--- /dev/null
+++ b/packages/url_launcher/url_launcher_web/example/build.yaml
@@ -0,0 +1,6 @@
+targets:
+  $default:
+    sources:
+      - integration_test/*.dart
+      - lib/$lib$
+      - $package$
diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart b/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart
new file mode 100644
index 0000000..3c1dbd8
--- /dev/null
+++ b/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart
@@ -0,0 +1,150 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:html' as html;
+import 'dart:js_util';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:url_launcher_platform_interface/link.dart';
+import 'package:url_launcher_web/src/link.dart';
+import 'package:integration_test/integration_test.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  group('Link Widget', () {
+    testWidgets('creates anchor with correct attributes',
+        (WidgetTester tester) async {
+      final Uri uri = Uri.parse('http://foobar/example?q=1');
+      await tester.pumpWidget(Directionality(
+        textDirection: TextDirection.ltr,
+        child: WebLinkDelegate(TestLinkInfo(
+          uri: uri,
+          target: LinkTarget.blank,
+          builder: (BuildContext context, FollowLink? followLink) {
+            return Container(width: 100, height: 100);
+          },
+        )),
+      ));
+      // Platform view creation happens asynchronously.
+      await tester.pumpAndSettle();
+
+      final html.Element anchor = _findSingleAnchor();
+      expect(anchor.getAttribute('href'), uri.toString());
+      expect(anchor.getAttribute('target'), '_blank');
+
+      final Uri uri2 = Uri.parse('http://foobar2/example?q=2');
+      await tester.pumpWidget(Directionality(
+        textDirection: TextDirection.ltr,
+        child: WebLinkDelegate(TestLinkInfo(
+          uri: uri2,
+          target: LinkTarget.self,
+          builder: (BuildContext context, FollowLink? followLink) {
+            return Container(width: 100, height: 100);
+          },
+        )),
+      ));
+      await tester.pumpAndSettle();
+
+      // Check that the same anchor has been updated.
+      expect(anchor.getAttribute('href'), uri2.toString());
+      expect(anchor.getAttribute('target'), '_self');
+    });
+
+    testWidgets('sizes itself correctly', (WidgetTester tester) async {
+      final Key containerKey = GlobalKey();
+      final Uri uri = Uri.parse('http://foobar');
+      await tester.pumpWidget(Directionality(
+        textDirection: TextDirection.ltr,
+        child: Center(
+          child: ConstrainedBox(
+            constraints: BoxConstraints.tight(Size(100.0, 100.0)),
+            child: WebLinkDelegate(TestLinkInfo(
+              uri: uri,
+              target: LinkTarget.blank,
+              builder: (BuildContext context, FollowLink? followLink) {
+                return Container(
+                  key: containerKey,
+                  child: SizedBox(width: 50.0, height: 50.0),
+                );
+              },
+            )),
+          ),
+        ),
+      ));
+      await tester.pumpAndSettle();
+
+      final Size containerSize = tester.getSize(find.byKey(containerKey));
+      // The Stack widget inserted by the `WebLinkDelegate` shouldn't loosen the
+      // constraints before passing them to the inner container. So the inner
+      // container should respect the tight constraints given by the ancestor
+      // `ConstrainedBox` widget.
+      expect(containerSize.width, 100.0);
+      expect(containerSize.height, 100.0);
+    });
+
+    // See: https://github.com/flutter/plugins/pull/3522#discussion_r574703724
+    testWidgets('uri can be null', (WidgetTester tester) async {
+      await tester.pumpWidget(Directionality(
+        textDirection: TextDirection.ltr,
+        child: WebLinkDelegate(TestLinkInfo(
+          uri: null,
+          target: LinkTarget.defaultTarget,
+          builder: (BuildContext context, FollowLink? followLink) {
+            return Container(width: 100, height: 100);
+          },
+        )),
+      ));
+      // Platform view creation happens asynchronously.
+      await tester.pumpAndSettle();
+
+      final html.Element anchor = _findSingleAnchor();
+      expect(anchor.hasAttribute('href'), false);
+    });
+  });
+}
+
+html.Element _findSingleAnchor() {
+  final List<html.Element> foundAnchors = <html.Element>[];
+  for (final html.Element anchor in html.document.querySelectorAll('a')) {
+    if (hasProperty(anchor, linkViewIdProperty)) {
+      foundAnchors.add(anchor);
+    }
+  }
+
+  // Search inside platform views with shadow roots as well.
+  for (final html.Element platformView
+      in html.document.querySelectorAll('flt-platform-view')) {
+    final html.ShadowRoot shadowRoot = platformView.shadowRoot!;
+    if (shadowRoot != null) {
+      for (final html.Element anchor in shadowRoot.querySelectorAll('a')) {
+        if (hasProperty(anchor, linkViewIdProperty)) {
+          foundAnchors.add(anchor);
+        }
+      }
+    }
+  }
+
+  return foundAnchors.single;
+}
+
+class TestLinkInfo extends LinkInfo {
+  @override
+  final LinkWidgetBuilder builder;
+
+  @override
+  final Uri? uri;
+
+  @override
+  final LinkTarget target;
+
+  @override
+  bool get isDisabled => uri == null;
+
+  TestLinkInfo({
+    required this.uri,
+    required this.target,
+    required this.builder,
+  });
+}
diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart
new file mode 100644
index 0000000..f7ea356
--- /dev/null
+++ b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart
@@ -0,0 +1,202 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:html' as html;
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:url_launcher_web/url_launcher_web.dart';
+import 'package:mockito/mockito.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'url_launcher_web_test.mocks.dart';
+
+@GenerateMocks([html.Window, html.Navigator])
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  group('UrlLauncherPlugin', () {
+    late MockWindow mockWindow;
+    late MockNavigator mockNavigator;
+
+    late UrlLauncherPlugin plugin;
+
+    setUp(() {
+      mockWindow = MockWindow();
+      mockNavigator = MockNavigator();
+      when(mockWindow.navigator).thenReturn(mockNavigator);
+
+      // Simulate that window.open does something.
+      when(mockWindow.open(any, any)).thenReturn(MockWindow());
+
+      when(mockNavigator.vendor).thenReturn('Google LLC');
+      when(mockNavigator.appVersion).thenReturn('Mock version!');
+
+      plugin = UrlLauncherPlugin(debugWindow: mockWindow);
+    });
+
+    group('canLaunch', () {
+      testWidgets('"http" URLs -> true', (WidgetTester _) async {
+        expect(plugin.canLaunch('http://google.com'), completion(isTrue));
+      });
+
+      testWidgets('"https" URLs -> true', (WidgetTester _) async {
+        expect(
+            plugin.canLaunch('https://go, (Widogle.com'), completion(isTrue));
+      });
+
+      testWidgets('"mailto" URLs -> true', (WidgetTester _) async {
+        expect(
+            plugin.canLaunch('mailto:name@mydomain.com'), completion(isTrue));
+      });
+
+      testWidgets('"tel" URLs -> true', (WidgetTester _) async {
+        expect(plugin.canLaunch('tel:5551234567'), completion(isTrue));
+      });
+
+      testWidgets('"sms" URLs -> true', (WidgetTester _) async {
+        expect(plugin.canLaunch('sms:+19725551212?body=hello%20there'),
+            completion(isTrue));
+      });
+    });
+
+    group('launch', () {
+      testWidgets('launching a URL returns true', (WidgetTester _) async {
+        expect(
+            plugin.launch(
+              'https://www.google.com',
+            ),
+            completion(isTrue));
+      });
+
+      testWidgets('launching a "mailto" returns true', (WidgetTester _) async {
+        expect(
+            plugin.launch(
+              'mailto:name@mydomain.com',
+            ),
+            completion(isTrue));
+      });
+
+      testWidgets('launching a "tel" returns true', (WidgetTester _) async {
+        expect(
+            plugin.launch(
+              'tel:5551234567',
+            ),
+            completion(isTrue));
+      });
+
+      testWidgets('launching a "sms" returns true', (WidgetTester _) async {
+        expect(
+            plugin.launch(
+              'sms:+19725551212?body=hello%20there',
+            ),
+            completion(isTrue));
+      });
+    });
+
+    group('openNewWindow', () {
+      testWidgets('http urls should be launched in a new window',
+          (WidgetTester _) async {
+        plugin.openNewWindow('http://www.google.com');
+
+        verify(mockWindow.open('http://www.google.com', ''));
+      });
+
+      testWidgets('https urls should be launched in a new window',
+          (WidgetTester _) async {
+        plugin.openNewWindow('https://www.google.com');
+
+        verify(mockWindow.open('https://www.google.com', ''));
+      });
+
+      testWidgets('mailto urls should be launched on a new window',
+          (WidgetTester _) async {
+        plugin.openNewWindow('mailto:name@mydomain.com');
+
+        verify(mockWindow.open('mailto:name@mydomain.com', ''));
+      });
+
+      testWidgets('tel urls should be launched on a new window',
+          (WidgetTester _) async {
+        plugin.openNewWindow('tel:5551234567');
+
+        verify(mockWindow.open('tel:5551234567', ''));
+      });
+
+      testWidgets('sms urls should be launched on a new window',
+          (WidgetTester _) async {
+        plugin.openNewWindow('sms:+19725551212?body=hello%20there');
+
+        verify(mockWindow.open('sms:+19725551212?body=hello%20there', ''));
+      });
+      testWidgets(
+          'setting webOnlyLinkTarget as _self opens the url in the same tab',
+          (WidgetTester _) async {
+        plugin.openNewWindow('https://www.google.com',
+            webOnlyWindowName: '_self');
+        verify(mockWindow.open('https://www.google.com', '_self'));
+      });
+
+      testWidgets(
+          'setting webOnlyLinkTarget as _blank opens the url in a new tab',
+          (WidgetTester _) async {
+        plugin.openNewWindow('https://www.google.com',
+            webOnlyWindowName: '_blank');
+        verify(mockWindow.open('https://www.google.com', '_blank'));
+      });
+
+      group('Safari', () {
+        setUp(() {
+          when(mockNavigator.vendor).thenReturn('Apple Computer, Inc.');
+          when(mockNavigator.appVersion).thenReturn(
+              '5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15');
+          // Recreate the plugin, so it grabs the overrides from this group
+          plugin = UrlLauncherPlugin(debugWindow: mockWindow);
+        });
+
+        testWidgets('http urls should be launched in a new window',
+            (WidgetTester _) async {
+          plugin.openNewWindow('http://www.google.com');
+
+          verify(mockWindow.open('http://www.google.com', ''));
+        });
+
+        testWidgets('https urls should be launched in a new window',
+            (WidgetTester _) async {
+          plugin.openNewWindow('https://www.google.com');
+
+          verify(mockWindow.open('https://www.google.com', ''));
+        });
+
+        testWidgets('mailto urls should be launched on the same window',
+            (WidgetTester _) async {
+          plugin.openNewWindow('mailto:name@mydomain.com');
+
+          verify(mockWindow.open('mailto:name@mydomain.com', '_top'));
+        });
+
+        testWidgets('tel urls should be launched on the same window',
+            (WidgetTester _) async {
+          plugin.openNewWindow('tel:5551234567');
+
+          verify(mockWindow.open('tel:5551234567', '_top'));
+        });
+
+        testWidgets('sms urls should be launched on the same window',
+            (WidgetTester _) async {
+          plugin.openNewWindow('sms:+19725551212?body=hello%20there');
+
+          verify(
+              mockWindow.open('sms:+19725551212?body=hello%20there', '_top'));
+        });
+        testWidgets(
+            'mailto urls should use _blank if webOnlyWindowName is set as _blank',
+            (WidgetTester _) async {
+          plugin.openNewWindow('mailto:name@mydomain.com',
+              webOnlyWindowName: '_blank');
+          verify(mockWindow.open('mailto:name@mydomain.com', '_blank'));
+        });
+      });
+    });
+  });
+}
diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.mocks.dart b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.mocks.dart
new file mode 100644
index 0000000..73d3bf3
--- /dev/null
+++ b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.mocks.dart
@@ -0,0 +1,660 @@
+import 'dart:async' as _i4;
+import 'dart:html' as _i2;
+import 'dart:math' as _i5;
+import 'dart:web_sql' as _i3;
+
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: comment_references
+
+// ignore_for_file: unnecessary_parenthesis
+
+class _FakeDocument extends _i1.Fake implements _i2.Document {}
+
+class _FakeLocation extends _i1.Fake implements _i2.Location {}
+
+class _FakeConsole extends _i1.Fake implements _i2.Console {}
+
+class _FakeHistory extends _i1.Fake implements _i2.History {}
+
+class _FakeStorage extends _i1.Fake implements _i2.Storage {}
+
+class _FakeNavigator extends _i1.Fake implements _i2.Navigator {}
+
+class _FakePerformance extends _i1.Fake implements _i2.Performance {}
+
+class _FakeEvents extends _i1.Fake implements _i2.Events {}
+
+class _FakeType extends _i1.Fake implements Type {}
+
+class _FakeWindowBase extends _i1.Fake implements _i2.WindowBase {}
+
+class _FakeFileSystem extends _i1.Fake implements _i2.FileSystem {}
+
+class _FakeStylePropertyMapReadonly extends _i1.Fake
+    implements _i2.StylePropertyMapReadonly {}
+
+class _FakeMediaQueryList extends _i1.Fake implements _i2.MediaQueryList {}
+
+class _FakeEntry extends _i1.Fake implements _i2.Entry {}
+
+class _FakeSqlDatabase extends _i1.Fake implements _i3.SqlDatabase {}
+
+class _FakeGeolocation extends _i1.Fake implements _i2.Geolocation {}
+
+class _FakeMediaStream extends _i1.Fake implements _i2.MediaStream {}
+
+class _FakeRelatedApplication extends _i1.Fake
+    implements _i2.RelatedApplication {}
+
+/// A class which mocks [Window].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockWindow extends _i1.Mock implements _i2.Window {
+  MockWindow() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  _i4.Future<num> get animationFrame =>
+      (super.noSuchMethod(Invocation.getter(#animationFrame), Future.value(0))
+          as _i4.Future<num>);
+  @override
+  _i2.Document get document =>
+      (super.noSuchMethod(Invocation.getter(#document), _FakeDocument())
+          as _i2.Document);
+  @override
+  _i2.Location get location =>
+      (super.noSuchMethod(Invocation.getter(#location), _FakeLocation())
+          as _i2.Location);
+  @override
+  set location(_i2.LocationBase? value) =>
+      super.noSuchMethod(Invocation.setter(#location, value));
+  @override
+  _i2.Console get console =>
+      (super.noSuchMethod(Invocation.getter(#console), _FakeConsole())
+          as _i2.Console);
+  @override
+  num get devicePixelRatio =>
+      (super.noSuchMethod(Invocation.getter(#devicePixelRatio), 0) as num);
+  @override
+  _i2.History get history =>
+      (super.noSuchMethod(Invocation.getter(#history), _FakeHistory())
+          as _i2.History);
+  @override
+  _i2.Storage get localStorage =>
+      (super.noSuchMethod(Invocation.getter(#localStorage), _FakeStorage())
+          as _i2.Storage);
+  @override
+  _i2.Navigator get navigator =>
+      (super.noSuchMethod(Invocation.getter(#navigator), _FakeNavigator())
+          as _i2.Navigator);
+  @override
+  int get outerHeight =>
+      (super.noSuchMethod(Invocation.getter(#outerHeight), 0) as int);
+  @override
+  int get outerWidth =>
+      (super.noSuchMethod(Invocation.getter(#outerWidth), 0) as int);
+  @override
+  _i2.Performance get performance =>
+      (super.noSuchMethod(Invocation.getter(#performance), _FakePerformance())
+          as _i2.Performance);
+  @override
+  _i2.Storage get sessionStorage =>
+      (super.noSuchMethod(Invocation.getter(#sessionStorage), _FakeStorage())
+          as _i2.Storage);
+  @override
+  _i4.Stream<_i2.Event> get onContentLoaded => (super.noSuchMethod(
+          Invocation.getter(#onContentLoaded), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onAbort => (super
+          .noSuchMethod(Invocation.getter(#onAbort), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onBlur =>
+      (super.noSuchMethod(Invocation.getter(#onBlur), Stream<_i2.Event>.empty())
+          as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onCanPlay => (super.noSuchMethod(
+          Invocation.getter(#onCanPlay), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onCanPlayThrough => (super.noSuchMethod(
+          Invocation.getter(#onCanPlayThrough), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onChange => (super
+          .noSuchMethod(Invocation.getter(#onChange), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onClick => (super.noSuchMethod(
+          Invocation.getter(#onClick), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onContextMenu => (super.noSuchMethod(
+          Invocation.getter(#onContextMenu), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.Event> get onDoubleClick => (super.noSuchMethod(
+          Invocation.getter(#onDoubleClick), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.DeviceMotionEvent> get onDeviceMotion => (super.noSuchMethod(
+          Invocation.getter(#onDeviceMotion),
+          Stream<_i2.DeviceMotionEvent>.empty())
+      as _i4.Stream<_i2.DeviceMotionEvent>);
+  @override
+  _i4.Stream<_i2.DeviceOrientationEvent> get onDeviceOrientation =>
+      (super.noSuchMethod(Invocation.getter(#onDeviceOrientation),
+              Stream<_i2.DeviceOrientationEvent>.empty())
+          as _i4.Stream<_i2.DeviceOrientationEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onDrag => (super.noSuchMethod(
+          Invocation.getter(#onDrag), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onDragEnd => (super.noSuchMethod(
+          Invocation.getter(#onDragEnd), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onDragEnter => (super.noSuchMethod(
+          Invocation.getter(#onDragEnter), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onDragLeave => (super.noSuchMethod(
+          Invocation.getter(#onDragLeave), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onDragOver => (super.noSuchMethod(
+          Invocation.getter(#onDragOver), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onDragStart => (super.noSuchMethod(
+          Invocation.getter(#onDragStart), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onDrop => (super.noSuchMethod(
+          Invocation.getter(#onDrop), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.Event> get onDurationChange => (super.noSuchMethod(
+          Invocation.getter(#onDurationChange), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onEmptied => (super.noSuchMethod(
+          Invocation.getter(#onEmptied), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onEnded => (super
+          .noSuchMethod(Invocation.getter(#onEnded), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onError => (super
+          .noSuchMethod(Invocation.getter(#onError), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onFocus => (super
+          .noSuchMethod(Invocation.getter(#onFocus), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onHashChange => (super.noSuchMethod(
+          Invocation.getter(#onHashChange), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onInput => (super
+          .noSuchMethod(Invocation.getter(#onInput), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onInvalid => (super.noSuchMethod(
+          Invocation.getter(#onInvalid), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.KeyboardEvent> get onKeyDown => (super.noSuchMethod(
+          Invocation.getter(#onKeyDown), Stream<_i2.KeyboardEvent>.empty())
+      as _i4.Stream<_i2.KeyboardEvent>);
+  @override
+  _i4.Stream<_i2.KeyboardEvent> get onKeyPress => (super.noSuchMethod(
+          Invocation.getter(#onKeyPress), Stream<_i2.KeyboardEvent>.empty())
+      as _i4.Stream<_i2.KeyboardEvent>);
+  @override
+  _i4.Stream<_i2.KeyboardEvent> get onKeyUp => (super.noSuchMethod(
+          Invocation.getter(#onKeyUp), Stream<_i2.KeyboardEvent>.empty())
+      as _i4.Stream<_i2.KeyboardEvent>);
+  @override
+  _i4.Stream<_i2.Event> get onLoad =>
+      (super.noSuchMethod(Invocation.getter(#onLoad), Stream<_i2.Event>.empty())
+          as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onLoadedData => (super.noSuchMethod(
+          Invocation.getter(#onLoadedData), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onLoadedMetadata => (super.noSuchMethod(
+          Invocation.getter(#onLoadedMetadata), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onLoadStart => (super.noSuchMethod(
+          Invocation.getter(#onLoadStart), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.MessageEvent> get onMessage => (super.noSuchMethod(
+          Invocation.getter(#onMessage), Stream<_i2.MessageEvent>.empty())
+      as _i4.Stream<_i2.MessageEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onMouseDown => (super.noSuchMethod(
+          Invocation.getter(#onMouseDown), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onMouseEnter => (super.noSuchMethod(
+          Invocation.getter(#onMouseEnter), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onMouseLeave => (super.noSuchMethod(
+          Invocation.getter(#onMouseLeave), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onMouseMove => (super.noSuchMethod(
+          Invocation.getter(#onMouseMove), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onMouseOut => (super.noSuchMethod(
+          Invocation.getter(#onMouseOut), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onMouseOver => (super.noSuchMethod(
+          Invocation.getter(#onMouseOver), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.MouseEvent> get onMouseUp => (super.noSuchMethod(
+          Invocation.getter(#onMouseUp), Stream<_i2.MouseEvent>.empty())
+      as _i4.Stream<_i2.MouseEvent>);
+  @override
+  _i4.Stream<_i2.WheelEvent> get onMouseWheel => (super.noSuchMethod(
+          Invocation.getter(#onMouseWheel), Stream<_i2.WheelEvent>.empty())
+      as _i4.Stream<_i2.WheelEvent>);
+  @override
+  _i4.Stream<_i2.Event> get onOffline => (super.noSuchMethod(
+          Invocation.getter(#onOffline), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onOnline => (super
+          .noSuchMethod(Invocation.getter(#onOnline), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onPageHide => (super.noSuchMethod(
+          Invocation.getter(#onPageHide), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onPageShow => (super.noSuchMethod(
+          Invocation.getter(#onPageShow), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onPause => (super
+          .noSuchMethod(Invocation.getter(#onPause), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onPlay =>
+      (super.noSuchMethod(Invocation.getter(#onPlay), Stream<_i2.Event>.empty())
+          as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onPlaying => (super.noSuchMethod(
+          Invocation.getter(#onPlaying), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.PopStateEvent> get onPopState => (super.noSuchMethod(
+          Invocation.getter(#onPopState), Stream<_i2.PopStateEvent>.empty())
+      as _i4.Stream<_i2.PopStateEvent>);
+  @override
+  _i4.Stream<_i2.Event> get onProgress => (super.noSuchMethod(
+          Invocation.getter(#onProgress), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onRateChange => (super.noSuchMethod(
+          Invocation.getter(#onRateChange), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onReset => (super
+          .noSuchMethod(Invocation.getter(#onReset), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onResize => (super
+          .noSuchMethod(Invocation.getter(#onResize), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onScroll => (super
+          .noSuchMethod(Invocation.getter(#onScroll), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onSearch => (super
+          .noSuchMethod(Invocation.getter(#onSearch), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onSeeked => (super
+          .noSuchMethod(Invocation.getter(#onSeeked), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onSeeking => (super.noSuchMethod(
+          Invocation.getter(#onSeeking), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onSelect => (super
+          .noSuchMethod(Invocation.getter(#onSelect), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onStalled => (super.noSuchMethod(
+          Invocation.getter(#onStalled), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.StorageEvent> get onStorage => (super.noSuchMethod(
+          Invocation.getter(#onStorage), Stream<_i2.StorageEvent>.empty())
+      as _i4.Stream<_i2.StorageEvent>);
+  @override
+  _i4.Stream<_i2.Event> get onSubmit => (super
+          .noSuchMethod(Invocation.getter(#onSubmit), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onSuspend => (super.noSuchMethod(
+          Invocation.getter(#onSuspend), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onTimeUpdate => (super.noSuchMethod(
+          Invocation.getter(#onTimeUpdate), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.TouchEvent> get onTouchCancel => (super.noSuchMethod(
+          Invocation.getter(#onTouchCancel), Stream<_i2.TouchEvent>.empty())
+      as _i4.Stream<_i2.TouchEvent>);
+  @override
+  _i4.Stream<_i2.TouchEvent> get onTouchEnd => (super.noSuchMethod(
+          Invocation.getter(#onTouchEnd), Stream<_i2.TouchEvent>.empty())
+      as _i4.Stream<_i2.TouchEvent>);
+  @override
+  _i4.Stream<_i2.TouchEvent> get onTouchMove => (super.noSuchMethod(
+          Invocation.getter(#onTouchMove), Stream<_i2.TouchEvent>.empty())
+      as _i4.Stream<_i2.TouchEvent>);
+  @override
+  _i4.Stream<_i2.TouchEvent> get onTouchStart => (super.noSuchMethod(
+          Invocation.getter(#onTouchStart), Stream<_i2.TouchEvent>.empty())
+      as _i4.Stream<_i2.TouchEvent>);
+  @override
+  _i4.Stream<_i2.TransitionEvent> get onTransitionEnd => (super.noSuchMethod(
+      Invocation.getter(#onTransitionEnd),
+      Stream<_i2.TransitionEvent>.empty()) as _i4.Stream<_i2.TransitionEvent>);
+  @override
+  _i4.Stream<_i2.Event> get onUnload => (super
+          .noSuchMethod(Invocation.getter(#onUnload), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onVolumeChange => (super.noSuchMethod(
+          Invocation.getter(#onVolumeChange), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.Event> get onWaiting => (super.noSuchMethod(
+          Invocation.getter(#onWaiting), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.AnimationEvent> get onAnimationEnd => (super.noSuchMethod(
+      Invocation.getter(#onAnimationEnd),
+      Stream<_i2.AnimationEvent>.empty()) as _i4.Stream<_i2.AnimationEvent>);
+  @override
+  _i4.Stream<_i2.AnimationEvent> get onAnimationIteration =>
+      (super.noSuchMethod(Invocation.getter(#onAnimationIteration),
+              Stream<_i2.AnimationEvent>.empty())
+          as _i4.Stream<_i2.AnimationEvent>);
+  @override
+  _i4.Stream<_i2.AnimationEvent> get onAnimationStart => (super.noSuchMethod(
+      Invocation.getter(#onAnimationStart),
+      Stream<_i2.AnimationEvent>.empty()) as _i4.Stream<_i2.AnimationEvent>);
+  @override
+  _i4.Stream<_i2.Event> get onBeforeUnload => (super.noSuchMethod(
+          Invocation.getter(#onBeforeUnload), Stream<_i2.Event>.empty())
+      as _i4.Stream<_i2.Event>);
+  @override
+  _i4.Stream<_i2.WheelEvent> get onWheel => (super.noSuchMethod(
+          Invocation.getter(#onWheel), Stream<_i2.WheelEvent>.empty())
+      as _i4.Stream<_i2.WheelEvent>);
+  @override
+  int get pageXOffset =>
+      (super.noSuchMethod(Invocation.getter(#pageXOffset), 0) as int);
+  @override
+  int get pageYOffset =>
+      (super.noSuchMethod(Invocation.getter(#pageYOffset), 0) as int);
+  @override
+  int get scrollX =>
+      (super.noSuchMethod(Invocation.getter(#scrollX), 0) as int);
+  @override
+  int get scrollY =>
+      (super.noSuchMethod(Invocation.getter(#scrollY), 0) as int);
+  @override
+  _i2.Events get on =>
+      (super.noSuchMethod(Invocation.getter(#on), _FakeEvents()) as _i2.Events);
+  @override
+  int get hashCode =>
+      (super.noSuchMethod(Invocation.getter(#hashCode), 0) as int);
+  @override
+  Type get runtimeType =>
+      (super.noSuchMethod(Invocation.getter(#runtimeType), _FakeType())
+          as Type);
+  @override
+  _i2.WindowBase open(String? url, String? name, [String? options]) =>
+      (super.noSuchMethod(
+              Invocation.method(#open, [url, name, options]), _FakeWindowBase())
+          as _i2.WindowBase);
+  @override
+  int requestAnimationFrame(_i2.FrameRequestCallback? callback) =>
+      (super.noSuchMethod(
+          Invocation.method(#requestAnimationFrame, [callback]), 0) as int);
+  @override
+  void cancelAnimationFrame(int? id) =>
+      super.noSuchMethod(Invocation.method(#cancelAnimationFrame, [id]));
+  @override
+  _i4.Future<_i2.FileSystem> requestFileSystem(int? size, {bool? persistent}) =>
+      (super.noSuchMethod(
+          Invocation.method(
+              #requestFileSystem, [size], {#persistent: persistent}),
+          Future.value(_FakeFileSystem())) as _i4.Future<_i2.FileSystem>);
+  @override
+  void cancelIdleCallback(int? handle) =>
+      super.noSuchMethod(Invocation.method(#cancelIdleCallback, [handle]));
+  @override
+  bool confirm([String? message]) =>
+      (super.noSuchMethod(Invocation.method(#confirm, [message]), false)
+          as bool);
+  @override
+  _i4.Future<dynamic> fetch(dynamic input, [Map<dynamic, dynamic>? init]) =>
+      (super.noSuchMethod(
+              Invocation.method(#fetch, [input, init]), Future.value(null))
+          as _i4.Future<dynamic>);
+  @override
+  bool find(String? string, bool? caseSensitive, bool? backwards, bool? wrap,
+          bool? wholeWord, bool? searchInFrames, bool? showDialog) =>
+      (super.noSuchMethod(
+          Invocation.method(#find, [
+            string,
+            caseSensitive,
+            backwards,
+            wrap,
+            wholeWord,
+            searchInFrames,
+            showDialog
+          ]),
+          false) as bool);
+  @override
+  _i2.StylePropertyMapReadonly getComputedStyleMap(
+          _i2.Element? element, String? pseudoElement) =>
+      (super.noSuchMethod(
+          Invocation.method(#getComputedStyleMap, [element, pseudoElement]),
+          _FakeStylePropertyMapReadonly()) as _i2.StylePropertyMapReadonly);
+  @override
+  List<_i2.CssRule> getMatchedCssRules(
+          _i2.Element? element, String? pseudoElement) =>
+      (super.noSuchMethod(
+          Invocation.method(#getMatchedCssRules, [element, pseudoElement]),
+          <_i2.CssRule>[]) as List<_i2.CssRule>);
+  @override
+  _i2.MediaQueryList matchMedia(String? query) => (super.noSuchMethod(
+          Invocation.method(#matchMedia, [query]), _FakeMediaQueryList())
+      as _i2.MediaQueryList);
+  @override
+  void moveBy(int? x, int? y) =>
+      super.noSuchMethod(Invocation.method(#moveBy, [x, y]));
+  @override
+  void postMessage(dynamic message, String? targetOrigin,
+          [List<Object>? transfer]) =>
+      super.noSuchMethod(
+          Invocation.method(#postMessage, [message, targetOrigin, transfer]));
+  @override
+  int requestIdleCallback(_i2.IdleRequestCallback? callback,
+          [Map<dynamic, dynamic>? options]) =>
+      (super.noSuchMethod(
+              Invocation.method(#requestIdleCallback, [callback, options]), 0)
+          as int);
+  @override
+  void resizeBy(int? x, int? y) =>
+      super.noSuchMethod(Invocation.method(#resizeBy, [x, y]));
+  @override
+  void resizeTo(int? x, int? y) =>
+      super.noSuchMethod(Invocation.method(#resizeTo, [x, y]));
+  @override
+  _i4.Future<_i2.Entry> resolveLocalFileSystemUrl(String? url) =>
+      (super.noSuchMethod(Invocation.method(#resolveLocalFileSystemUrl, [url]),
+          Future.value(_FakeEntry())) as _i4.Future<_i2.Entry>);
+  @override
+  String atob(String? atob) =>
+      (super.noSuchMethod(Invocation.method(#atob, [atob]), '') as String);
+  @override
+  String btoa(String? btoa) =>
+      (super.noSuchMethod(Invocation.method(#btoa, [btoa]), '') as String);
+  @override
+  void moveTo(_i5.Point<num>? p) =>
+      super.noSuchMethod(Invocation.method(#moveTo, [p]));
+  @override
+  _i3.SqlDatabase openDatabase(String? name, String? version,
+          String? displayName, int? estimatedSize,
+          [_i2.DatabaseCallback? creationCallback]) =>
+      (super.noSuchMethod(
+          Invocation.method(#openDatabase,
+              [name, version, displayName, estimatedSize, creationCallback]),
+          _FakeSqlDatabase()) as _i3.SqlDatabase);
+  @override
+  void addEventListener(String? type, _i2.EventListener? listener,
+          [bool? useCapture]) =>
+      super.noSuchMethod(
+          Invocation.method(#addEventListener, [type, listener, useCapture]));
+  @override
+  void removeEventListener(String? type, _i2.EventListener? listener,
+          [bool? useCapture]) =>
+      super.noSuchMethod(Invocation.method(
+          #removeEventListener, [type, listener, useCapture]));
+  @override
+  bool dispatchEvent(_i2.Event? event) =>
+      (super.noSuchMethod(Invocation.method(#dispatchEvent, [event]), false)
+          as bool);
+  @override
+  bool operator ==(Object? other) =>
+      (super.noSuchMethod(Invocation.method(#==, [other]), false) as bool);
+  @override
+  String toString() =>
+      (super.noSuchMethod(Invocation.method(#toString, []), '') as String);
+}
+
+/// A class which mocks [Navigator].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockNavigator extends _i1.Mock implements _i2.Navigator {
+  MockNavigator() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  String get language =>
+      (super.noSuchMethod(Invocation.getter(#language), '') as String);
+  @override
+  _i2.Geolocation get geolocation =>
+      (super.noSuchMethod(Invocation.getter(#geolocation), _FakeGeolocation())
+          as _i2.Geolocation);
+  @override
+  String get vendor =>
+      (super.noSuchMethod(Invocation.getter(#vendor), '') as String);
+  @override
+  String get vendorSub =>
+      (super.noSuchMethod(Invocation.getter(#vendorSub), '') as String);
+  @override
+  String get appCodeName =>
+      (super.noSuchMethod(Invocation.getter(#appCodeName), '') as String);
+  @override
+  String get appName =>
+      (super.noSuchMethod(Invocation.getter(#appName), '') as String);
+  @override
+  String get appVersion =>
+      (super.noSuchMethod(Invocation.getter(#appVersion), '') as String);
+  @override
+  String get product =>
+      (super.noSuchMethod(Invocation.getter(#product), '') as String);
+  @override
+  String get userAgent =>
+      (super.noSuchMethod(Invocation.getter(#userAgent), '') as String);
+  @override
+  int get hashCode =>
+      (super.noSuchMethod(Invocation.getter(#hashCode), 0) as int);
+  @override
+  Type get runtimeType =>
+      (super.noSuchMethod(Invocation.getter(#runtimeType), _FakeType())
+          as Type);
+  @override
+  List<_i2.Gamepad?> getGamepads() =>
+      (super.noSuchMethod(Invocation.method(#getGamepads, []), <_i2.Gamepad?>[])
+          as List<_i2.Gamepad?>);
+  @override
+  _i4.Future<_i2.MediaStream> getUserMedia({dynamic audio, dynamic video}) =>
+      (super.noSuchMethod(
+          Invocation.method(#getUserMedia, [], {#audio: audio, #video: video}),
+          Future.value(_FakeMediaStream())) as _i4.Future<_i2.MediaStream>);
+  @override
+  _i4.Future<dynamic> getBattery() => (super
+          .noSuchMethod(Invocation.method(#getBattery, []), Future.value(null))
+      as _i4.Future<dynamic>);
+  @override
+  _i4.Future<_i2.RelatedApplication> getInstalledRelatedApps() =>
+      (super.noSuchMethod(Invocation.method(#getInstalledRelatedApps, []),
+              Future.value(_FakeRelatedApplication()))
+          as _i4.Future<_i2.RelatedApplication>);
+  @override
+  _i4.Future<dynamic> getVRDisplays() => (super.noSuchMethod(
+          Invocation.method(#getVRDisplays, []), Future.value(null))
+      as _i4.Future<dynamic>);
+  @override
+  void registerProtocolHandler(String? scheme, String? url, String? title) =>
+      super.noSuchMethod(
+          Invocation.method(#registerProtocolHandler, [scheme, url, title]));
+  @override
+  _i4.Future<dynamic> requestKeyboardLock([List<String>? keyCodes]) =>
+      (super.noSuchMethod(Invocation.method(#requestKeyboardLock, [keyCodes]),
+          Future.value(null)) as _i4.Future<dynamic>);
+  @override
+  _i4.Future<dynamic> requestMidiAccess([Map<dynamic, dynamic>? options]) =>
+      (super.noSuchMethod(Invocation.method(#requestMidiAccess, [options]),
+          Future.value(null)) as _i4.Future<dynamic>);
+  @override
+  _i4.Future<dynamic> requestMediaKeySystemAccess(String? keySystem,
+          List<Map<dynamic, dynamic>>? supportedConfigurations) =>
+      (super.noSuchMethod(
+          Invocation.method(#requestMediaKeySystemAccess,
+              [keySystem, supportedConfigurations]),
+          Future.value(null)) as _i4.Future<dynamic>);
+  @override
+  bool sendBeacon(String? url, Object? data) =>
+      (super.noSuchMethod(Invocation.method(#sendBeacon, [url, data]), false)
+          as bool);
+  @override
+  _i4.Future<dynamic> share([Map<dynamic, dynamic>? data]) =>
+      (super.noSuchMethod(Invocation.method(#share, [data]), Future.value(null))
+          as _i4.Future<dynamic>);
+  @override
+  bool operator ==(Object? other) =>
+      (super.noSuchMethod(Invocation.method(#==, [other]), false) as bool);
+  @override
+  String toString() =>
+      (super.noSuchMethod(Invocation.method(#toString, []), '') as String);
+}
diff --git a/packages/url_launcher/url_launcher_web/test/lib/main.dart b/packages/url_launcher/url_launcher_web/example/lib/main.dart
similarity index 100%
rename from packages/url_launcher/url_launcher_web/test/lib/main.dart
rename to packages/url_launcher/url_launcher_web/example/lib/main.dart
diff --git a/packages/url_launcher/url_launcher_web/test/pubspec.yaml b/packages/url_launcher/url_launcher_web/example/pubspec.yaml
similarity index 61%
rename from packages/url_launcher/url_launcher_web/test/pubspec.yaml
rename to packages/url_launcher/url_launcher_web/example/pubspec.yaml
index b8c541f..5fc060f 100644
--- a/packages/url_launcher/url_launcher_web/test/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_web/example/pubspec.yaml
@@ -1,22 +1,22 @@
 name: regular_integration_tests
 publish_to: none
 
-environment:
-  sdk: ">=2.10.0-56.0.dev <3.0.0"
-
 dependencies:
   flutter:
     sdk: flutter
 
 dev_dependencies:
+  build_runner: ^1.10.0
+  mockito: ^5.0.0-nullsafety.5
+  url_launcher_web:
+    path: ../
   flutter_driver:
     sdk: flutter
   flutter_test:
     sdk: flutter
-  http: ^0.12.2
-  mockito: ^4.1.1
-  url_launcher_web:
-    path: ../
   integration_test:
-    path: ../../../integration_test
+    sdk: flutter
 
+environment:
+  sdk: ">=2.12.0-0 <3.0.0"
+  flutter: ">=1.26.0-0" # For integration_test from sdk
diff --git a/packages/url_launcher/url_launcher_web/example/run_test.sh b/packages/url_launcher/url_launcher_web/example/run_test.sh
new file mode 100755
index 0000000..b243f29
--- /dev/null
+++ b/packages/url_launcher/url_launcher_web/example/run_test.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/bash
+if pgrep -lf chromedriver > /dev/null; then
+  echo "chromedriver is running."
+
+  flutter pub get
+
+  echo "(Re)generating mocks."
+  flutter pub run build_runner build --delete-conflicting-outputs
+
+  if [ $# -eq 0 ]; then
+    echo "No target specified, running all tests..."
+    find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test_driver.dart --target='{}'
+  else
+    echo "Running test target: $1..."
+    set -x
+    flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test_driver.dart --target=$1
+  fi
+
+  else
+    echo "chromedriver is not running."
+    echo "Please, check the README.md for instructions on how to use run_test.sh"
+fi
+
diff --git a/packages/url_launcher/url_launcher_web/test/test_driver/url_launcher_web_integration_test.dart b/packages/url_launcher/url_launcher_web/example/test_driver/integration_test_driver.dart
similarity index 94%
rename from packages/url_launcher/url_launcher_web/test/test_driver/url_launcher_web_integration_test.dart
rename to packages/url_launcher/url_launcher_web/example/test_driver/integration_test_driver.dart
index 2d68bb9..64e2248 100644
--- a/packages/url_launcher/url_launcher_web/test/test_driver/url_launcher_web_integration_test.dart
+++ b/packages/url_launcher/url_launcher_web/example/test_driver/integration_test_driver.dart
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// @dart = 2.9
 import 'package:integration_test/integration_test_driver.dart';
 
 Future<void> main() async => integrationDriver();
diff --git a/packages/url_launcher/url_launcher_web/test/web/index.html b/packages/url_launcher/url_launcher_web/example/web/index.html
similarity index 100%
rename from packages/url_launcher/url_launcher_web/test/web/index.html
rename to packages/url_launcher/url_launcher_web/example/web/index.html
diff --git a/packages/url_launcher/url_launcher_web/lib/src/link.dart b/packages/url_launcher/url_launcher_web/lib/src/link.dart
index 8169a9c..42c711b 100644
--- a/packages/url_launcher/url_launcher_web/lib/src/link.dart
+++ b/packages/url_launcher/url_launcher_web/lib/src/link.dart
@@ -45,16 +45,16 @@
 /// For external URIs, it lets the browser do its thing. For app route names, it
 /// pushes the route name to the framework.
 class WebLinkDelegateState extends State<WebLinkDelegate> {
-  LinkViewController _controller;
+  late LinkViewController _controller;
 
   @override
   void didUpdateWidget(WebLinkDelegate oldWidget) {
     super.didUpdateWidget(oldWidget);
     if (widget.link.uri != oldWidget.link.uri) {
-      _controller?.setUri(widget.link.uri);
+      _controller.setUri(widget.link.uri);
     }
     if (widget.link.target != oldWidget.link.target) {
-      _controller?.setTarget(widget.link.target);
+      _controller.setTarget(widget.link.target);
     }
   }
 
@@ -126,15 +126,15 @@
   static Map<int, LinkViewController> _instances = <int, LinkViewController>{};
 
   static html.Element _viewFactory(int viewId) {
-    return _instances[viewId]?._element;
+    return _instances[viewId]!._element;
   }
 
-  static int _hitTestedViewId;
+  static int? _hitTestedViewId;
 
-  static StreamSubscription _clickSubscription;
+  static late StreamSubscription _clickSubscription;
 
   static void _onGlobalClick(html.MouseEvent event) {
-    final int viewId = getViewIdFromTarget(event);
+    final int? viewId = getViewIdFromTarget(event);
     _instances[viewId]?._onDomClick(event);
     // After the DOM click event has been received, clean up the hit test state
     // so we can start fresh on the next click.
@@ -161,7 +161,7 @@
   /// The context of the [Link] widget that created this controller.
   final BuildContext context;
 
-  html.Element _element;
+  late html.Element _element;
   bool get _isInitialized => _element != null;
 
   Future<void> _initialize() async {
@@ -193,7 +193,7 @@
       return;
     }
 
-    if (_uri.hasScheme) {
+    if (_uri != null && _uri!.hasScheme) {
       // External links will be handled by the browser, so we don't have to do
       // anything.
       return;
@@ -207,10 +207,12 @@
     pushRouteNameToFramework(context, routeName);
   }
 
-  Uri _uri;
+  Uri? _uri;
 
   /// Set the [Uri] value for this link.
-  void setUri(Uri uri) {
+  ///
+  /// When Uri is null, the `href` attribute of the link is removed.
+  void setUri(Uri? uri) {
     assert(_isInitialized);
     _uri = uri;
     if (uri == null) {
@@ -264,8 +266,8 @@
 }
 
 /// Finds the view id of the DOM element targeted by the [event].
-int getViewIdFromTarget(html.Event event) {
-  final html.Element linkElement = getLinkElementFromTarget(event);
+int? getViewIdFromTarget(html.Event event) {
+  final html.Element? linkElement = getLinkElementFromTarget(event);
   if (linkElement != null) {
     return getProperty(linkElement, linkViewIdProperty);
   }
@@ -275,15 +277,17 @@
 /// Finds the targeted DOM element by the [event].
 ///
 /// It handles the case where the target element is inside a shadow DOM too.
-html.Element getLinkElementFromTarget(html.Event event) {
-  final html.Element target = event.target;
-  if (isLinkElement(target)) {
-    return target;
-  }
-  if (target.shadowRoot != null) {
-    final html.Element child = target.shadowRoot.lastChild;
-    if (isLinkElement(child)) {
-      return child;
+html.Element? getLinkElementFromTarget(html.Event event) {
+  final html.EventTarget? target = event.target;
+  if (target != null && target is html.Element) {
+    if (isLinkElement(target)) {
+      return target;
+    }
+    if (target.shadowRoot != null) {
+      final html.Node? child = target.shadowRoot!.lastChild;
+      if (child != null && child is html.Element && isLinkElement(child)) {
+        return child;
+      }
     }
   }
   return null;
@@ -291,6 +295,8 @@
 
 /// Checks if the given [element] is a link that was created by
 /// [LinkViewController].
-bool isLinkElement(html.Element element) {
-  return element.tagName == 'A' && hasProperty(element, linkViewIdProperty);
+bool isLinkElement(html.Element? element) {
+  return element != null &&
+      element.tagName == 'A' &&
+      hasProperty(element, linkViewIdProperty);
 }
diff --git a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart
index 969cfba..e4d2116 100644
--- a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart
+++ b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart
@@ -19,7 +19,7 @@
   'tel',
   'sms',
 };
-String _getUrlScheme(String url) => Uri.tryParse(url)?.scheme;
+String? _getUrlScheme(String url) => Uri.tryParse(url)?.scheme;
 
 bool _isSafariTargetTopScheme(String url) =>
     _safariTargetTopSchemes.contains(_getUrlScheme(url));
@@ -38,7 +38,7 @@
   }.union(_safariTargetTopSchemes);
 
   /// A constructor that allows tests to override the window object used by the plugin.
-  UrlLauncherPlugin({@visibleForTesting html.Window debugWindow})
+  UrlLauncherPlugin({@visibleForTesting html.Window? debugWindow})
       : _window = debugWindow ?? html.window {
     _isSafari = navigatorIsSafari(_window.navigator);
   }
@@ -58,7 +58,7 @@
   ///
   /// Returns the newly created window.
   @visibleForTesting
-  html.WindowBase openNewWindow(String url, {String webOnlyWindowName}) {
+  html.WindowBase openNewWindow(String url, {String? webOnlyWindowName}) {
     // We need to open mailto, tel and sms urls on the _top window context on safari browsers.
     // See https://github.com/flutter/flutter/issues/51461 for reference.
     final target = webOnlyWindowName ??
@@ -74,13 +74,13 @@
   @override
   Future<bool> launch(
     String url, {
-    @required bool useSafariVC,
-    @required bool useWebView,
-    @required bool enableJavaScript,
-    @required bool enableDomStorage,
-    @required bool universalLinksOnly,
-    @required Map<String, String> headers,
-    String webOnlyWindowName,
+    bool useSafariVC = false,
+    bool useWebView = false,
+    bool enableJavaScript = false,
+    bool enableDomStorage = false,
+    bool universalLinksOnly = false,
+    Map<String, String> headers = const <String, String>{},
+    String? webOnlyWindowName,
   }) {
     return Future<bool>.value(
         openNewWindow(url, webOnlyWindowName: webOnlyWindowName) != null);
diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml
index 77a9586..b9f957a 100644
--- a/packages/url_launcher/url_launcher_web/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_web/pubspec.yaml
@@ -1,10 +1,7 @@
 name: url_launcher_web
 description: Web platform implementation of url_launcher
 homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_web
-# 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump
-# the version to 2.0.0.
-# See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0
-version: 0.1.5+3
+version: 2.0.0-nullsafety
 
 flutter:
   plugin:
@@ -14,31 +11,18 @@
         fileName: url_launcher_web.dart
 
 dependencies:
-  url_launcher_platform_interface: ^1.0.9
-  # TODO(mvanbeusekom): Update to use pub.dev once null safety version is published.
-  # url_launcher_platform_interface:
-  #   git:
-  #     url: https://github.com/flutter/plugins.git
-  #     ref: nnbd
-  #     path: packages/url_launcher/url_launcher_platform_interface
+  url_launcher_platform_interface: ^2.0.0-nullsafety
+  meta: ^1.3.0 # null safe
   flutter:
     sdk: flutter
   flutter_web_plugins:
     sdk: flutter
-  meta: ^1.1.7
 
 dev_dependencies:
+  pedantic: ^1.10.0 # null safe
   flutter_test:
     sdk: flutter
-  url_launcher: ^5.2.5
-  # TODO(mvanbeusekom): Update to use pub.dev once null safety version is published.
-  # url_launcher:
-  #   path: ../url_launcher
-  pedantic: ^1.8.0
-  mockito: ^4.1.1
-  integration_test:
-    path: ../../integration_test
 
 environment:
-  sdk: ">=2.2.0 <3.0.0"
-  flutter: ">=1.10.0"
+  sdk: ">=2.12.0-0 <3.0.0"
+  flutter: ">=1.12.13+hotfix.5"
diff --git a/packages/url_launcher/url_launcher_web/test/README.md b/packages/url_launcher/url_launcher_web/test/README.md
index 7c48d02..7c5b4ad 100644
--- a/packages/url_launcher/url_launcher_web/test/README.md
+++ b/packages/url_launcher/url_launcher_web/test/README.md
@@ -1,17 +1,5 @@
-# Running browser_tests
+## test
 
-Make sure you have updated to the latest Flutter master.
+This package uses integration tests for testing.
 
-1. Check what version of Chrome is running on the machine you're running tests on.
-
-2. Download and install driver for that version from here:
-    * <https://chromedriver.chromium.org/downloads>
-
-3. Start the driver using `chromedriver --port=4444`
-
-4. Change into the `test` directory of your clone.
-
-5. Run tests: `flutter drive -d web-server --browser-name=chrome --target=test_driver/TEST_NAME_integration.dart`, or (in Linux):
-
-    * Single: `./run_test test_driver/TEST_NAME_integration.dart`
-    * All: `./run_test`
+See `example/README.md` for more info.
diff --git a/packages/url_launcher/url_launcher_web/test/run_test b/packages/url_launcher/url_launcher_web/test/run_test
deleted file mode 100755
index 74a8526..0000000
--- a/packages/url_launcher/url_launcher_web/test/run_test
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/bash
-if pgrep -lf chromedriver > /dev/null; then
-  echo "chromedriver is running."
-
-  if [ $# -eq 0 ]; then
-    echo "No target specified, running all tests..."
-    find test_driver/ -iname *_integration.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --target='{}'
-  else
-    echo "Running test target: $1..."
-    set -x
-    flutter drive -d web-server --web-port=7357 --browser-name=chrome --target=$1
-  fi
-
-  else
-    echo "chromedriver is not running."
-fi
-
diff --git a/packages/url_launcher/url_launcher_web/test/test_driver/url_launcher_web_integration.dart b/packages/url_launcher/url_launcher_web/test/test_driver/url_launcher_web_integration.dart
deleted file mode 100644
index bfa9482..0000000
--- a/packages/url_launcher/url_launcher_web/test/test_driver/url_launcher_web_integration.dart
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// @dart = 2.9
-import 'dart:html' as html;
-import 'dart:js_util';
-import 'package:flutter/widgets.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:url_launcher_platform_interface/link.dart';
-import 'package:url_launcher_web/url_launcher_web.dart';
-import 'package:url_launcher_web/src/link.dart';
-import 'package:mockito/mockito.dart';
-import 'package:integration_test/integration_test.dart';
-
-class _MockWindow extends Mock implements html.Window {}
-
-class _MockNavigator extends Mock implements html.Navigator {}
-
-void main() {
-  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
-
-  group('UrlLauncherPlugin', () {
-    _MockWindow mockWindow;
-    _MockNavigator mockNavigator;
-
-    UrlLauncherPlugin plugin;
-
-    setUp(() {
-      mockWindow = _MockWindow();
-      mockNavigator = _MockNavigator();
-      when(mockWindow.navigator).thenReturn(mockNavigator);
-
-      plugin = UrlLauncherPlugin(debugWindow: mockWindow);
-    });
-
-    group('canLaunch', () {
-      testWidgets('"http" URLs -> true', (WidgetTester _) async {
-        expect(plugin.canLaunch('http://google.com'), completion(isTrue));
-      });
-
-      testWidgets('"https" URLs -> true', (WidgetTester _) async {
-        expect(
-            plugin.canLaunch('https://go, (Widogle.com'), completion(isTrue));
-      });
-
-      testWidgets('"mailto" URLs -> true', (WidgetTester _) async {
-        expect(
-            plugin.canLaunch('mailto:name@mydomain.com'), completion(isTrue));
-      });
-
-      testWidgets('"tel" URLs -> true', (WidgetTester _) async {
-        expect(plugin.canLaunch('tel:5551234567'), completion(isTrue));
-      });
-
-      testWidgets('"sms" URLs -> true', (WidgetTester _) async {
-        expect(plugin.canLaunch('sms:+19725551212?body=hello%20there'),
-            completion(isTrue));
-      });
-    });
-
-    group('launch', () {
-      setUp(() {
-        // Simulate that window.open does something.
-        when(mockWindow.open('https://www.google.com', ''))
-            .thenReturn(_MockWindow());
-        when(mockWindow.open('mailto:name@mydomain.com', ''))
-            .thenReturn(_MockWindow());
-        when(mockWindow.open('tel:5551234567', '')).thenReturn(_MockWindow());
-        when(mockWindow.open('sms:+19725551212?body=hello%20there', ''))
-            .thenReturn(_MockWindow());
-      });
-
-      testWidgets('launching a URL returns true', (WidgetTester _) async {
-        expect(
-            plugin.launch(
-              'https://www.google.com',
-              useSafariVC: null,
-              useWebView: null,
-              universalLinksOnly: null,
-              enableDomStorage: null,
-              enableJavaScript: null,
-              headers: null,
-            ),
-            completion(isTrue));
-      });
-
-      testWidgets('launching a "mailto" returns true', (WidgetTester _) async {
-        expect(
-            plugin.launch(
-              'mailto:name@mydomain.com',
-              useSafariVC: null,
-              useWebView: null,
-              universalLinksOnly: null,
-              enableDomStorage: null,
-              enableJavaScript: null,
-              headers: null,
-            ),
-            completion(isTrue));
-      });
-
-      testWidgets('launching a "tel" returns true', (WidgetTester _) async {
-        expect(
-            plugin.launch(
-              'tel:5551234567',
-              useSafariVC: null,
-              useWebView: null,
-              universalLinksOnly: null,
-              enableDomStorage: null,
-              enableJavaScript: null,
-              headers: null,
-            ),
-            completion(isTrue));
-      });
-
-      testWidgets('launching a "sms" returns true', (WidgetTester _) async {
-        expect(
-            plugin.launch(
-              'sms:+19725551212?body=hello%20there',
-              useSafariVC: null,
-              useWebView: null,
-              universalLinksOnly: null,
-              enableDomStorage: null,
-              enableJavaScript: null,
-              headers: null,
-            ),
-            completion(isTrue));
-      });
-    });
-
-    group('openNewWindow', () {
-      testWidgets('http urls should be launched in a new window',
-          (WidgetTester _) async {
-        plugin.openNewWindow('http://www.google.com');
-
-        verify(mockWindow.open('http://www.google.com', ''));
-      });
-
-      testWidgets('https urls should be launched in a new window',
-          (WidgetTester _) async {
-        plugin.openNewWindow('https://www.google.com');
-
-        verify(mockWindow.open('https://www.google.com', ''));
-      });
-
-      testWidgets('mailto urls should be launched on a new window',
-          (WidgetTester _) async {
-        plugin.openNewWindow('mailto:name@mydomain.com');
-
-        verify(mockWindow.open('mailto:name@mydomain.com', ''));
-      });
-
-      testWidgets('tel urls should be launched on a new window',
-          (WidgetTester _) async {
-        plugin.openNewWindow('tel:5551234567');
-
-        verify(mockWindow.open('tel:5551234567', ''));
-      });
-
-      testWidgets('sms urls should be launched on a new window',
-          (WidgetTester _) async {
-        plugin.openNewWindow('sms:+19725551212?body=hello%20there');
-
-        verify(mockWindow.open('sms:+19725551212?body=hello%20there', ''));
-      });
-      testWidgets(
-          'setting webOnlyLinkTarget as _self opens the url in the same tab',
-          (WidgetTester _) async {
-        plugin.openNewWindow('https://www.google.com',
-            webOnlyWindowName: '_self');
-        verify(mockWindow.open('https://www.google.com', '_self'));
-      });
-
-      testWidgets(
-          'setting webOnlyLinkTarget as _blank opens the url in a new tab',
-          (WidgetTester _) async {
-        plugin.openNewWindow('https://www.google.com',
-            webOnlyWindowName: '_blank');
-        verify(mockWindow.open('https://www.google.com', '_blank'));
-      });
-
-      group('Safari', () {
-        setUp(() {
-          when(mockNavigator.vendor).thenReturn('Apple Computer, Inc.');
-          when(mockNavigator.appVersion).thenReturn(
-              '5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15');
-          // Recreate the plugin, so it grabs the overrides from this group
-          plugin = UrlLauncherPlugin(debugWindow: mockWindow);
-        });
-
-        testWidgets('http urls should be launched in a new window',
-            (WidgetTester _) async {
-          plugin.openNewWindow('http://www.google.com');
-
-          verify(mockWindow.open('http://www.google.com', ''));
-        });
-
-        testWidgets('https urls should be launched in a new window',
-            (WidgetTester _) async {
-          plugin.openNewWindow('https://www.google.com');
-
-          verify(mockWindow.open('https://www.google.com', ''));
-        });
-
-        testWidgets('mailto urls should be launched on the same window',
-            (WidgetTester _) async {
-          plugin.openNewWindow('mailto:name@mydomain.com');
-
-          verify(mockWindow.open('mailto:name@mydomain.com', '_top'));
-        });
-
-        testWidgets('tel urls should be launched on the same window',
-            (WidgetTester _) async {
-          plugin.openNewWindow('tel:5551234567');
-
-          verify(mockWindow.open('tel:5551234567', '_top'));
-        });
-
-        testWidgets('sms urls should be launched on the same window',
-            (WidgetTester _) async {
-          plugin.openNewWindow('sms:+19725551212?body=hello%20there');
-
-          verify(
-              mockWindow.open('sms:+19725551212?body=hello%20there', '_top'));
-        });
-        testWidgets(
-            'mailto urls should use _blank if webOnlyWindowName is set as _blank',
-            (WidgetTester _) async {
-          plugin.openNewWindow('mailto:name@mydomain.com',
-              webOnlyWindowName: '_blank');
-          verify(mockWindow.open('mailto:name@mydomain.com', '_blank'));
-        });
-      });
-    });
-  });
-
-  group('link', () {
-    testWidgets('creates anchor with correct attributes',
-        (WidgetTester tester) async {
-      final Uri uri = Uri.parse('http://foobar/example?q=1');
-      await tester.pumpWidget(Directionality(
-        textDirection: TextDirection.ltr,
-        child: WebLinkDelegate(TestLinkInfo(
-          uri: uri,
-          target: LinkTarget.blank,
-          builder: (BuildContext context, FollowLink followLink) {
-            return Container(width: 100, height: 100);
-          },
-        )),
-      ));
-      // Platform view creation happens asynchronously.
-      await tester.pumpAndSettle();
-
-      final html.Element anchor = _findSingleAnchor();
-      expect(anchor.getAttribute('href'), uri.toString());
-      expect(anchor.getAttribute('target'), '_blank');
-
-      final Uri uri2 = Uri.parse('http://foobar2/example?q=2');
-      await tester.pumpWidget(Directionality(
-        textDirection: TextDirection.ltr,
-        child: WebLinkDelegate(TestLinkInfo(
-          uri: uri2,
-          target: LinkTarget.self,
-          builder: (BuildContext context, FollowLink followLink) {
-            return Container(width: 100, height: 100);
-          },
-        )),
-      ));
-      await tester.pumpAndSettle();
-
-      // Check that the same anchor has been updated.
-      expect(anchor.getAttribute('href'), uri2.toString());
-      expect(anchor.getAttribute('target'), '_self');
-    });
-
-    testWidgets('sizes itself correctly', (WidgetTester tester) async {
-      final Key containerKey = GlobalKey();
-      final Uri uri = Uri.parse('http://foobar');
-      await tester.pumpWidget(Directionality(
-        textDirection: TextDirection.ltr,
-        child: Center(
-          child: ConstrainedBox(
-            constraints: BoxConstraints.tight(Size(100.0, 100.0)),
-            child: WebLinkDelegate(TestLinkInfo(
-              uri: uri,
-              target: LinkTarget.blank,
-              builder: (BuildContext context, FollowLink followLink) {
-                return Container(
-                  key: containerKey,
-                  child: SizedBox(width: 50.0, height: 50.0),
-                );
-              },
-            )),
-          ),
-        ),
-      ));
-      await tester.pumpAndSettle();
-
-      final Size containerSize = tester.getSize(find.byKey(containerKey));
-      // The Stack widget inserted by the `WebLinkDelegate` shouldn't loosen the
-      // constraints before passing them to the inner container. So the inner
-      // container should respect the tight constraints given by the ancestor
-      // `ConstrainedBox` widget.
-      expect(containerSize.width, 100.0);
-      expect(containerSize.height, 100.0);
-    });
-  });
-}
-
-html.Element _findSingleAnchor() {
-  final List<html.Element> foundAnchors = <html.Element>[];
-  for (final html.Element anchor in html.document.querySelectorAll('a')) {
-    if (hasProperty(anchor, linkViewIdProperty)) {
-      foundAnchors.add(anchor);
-    }
-  }
-
-  // Search inside platform views with shadow roots as well.
-  for (final html.Element platformView
-      in html.document.querySelectorAll('flt-platform-view')) {
-    final html.ShadowRoot shadowRoot = platformView.shadowRoot;
-    if (shadowRoot != null) {
-      for (final html.Element anchor in shadowRoot.querySelectorAll('a')) {
-        if (hasProperty(anchor, linkViewIdProperty)) {
-          foundAnchors.add(anchor);
-        }
-      }
-    }
-  }
-
-  return foundAnchors.single;
-}
-
-class TestLinkInfo extends LinkInfo {
-  @override
-  final LinkWidgetBuilder builder;
-
-  @override
-  final Uri uri;
-
-  @override
-  final LinkTarget target;
-
-  @override
-  bool get isDisabled => uri == null;
-
-  TestLinkInfo({
-    @required this.uri,
-    @required this.target,
-    @required this.builder,
-  });
-}
diff --git a/packages/url_launcher/url_launcher_web/test/tests_exist_elsewhere_test.dart b/packages/url_launcher/url_launcher_web/test/tests_exist_elsewhere_test.dart
new file mode 100644
index 0000000..334f521
--- /dev/null
+++ b/packages/url_launcher/url_launcher_web/test/tests_exist_elsewhere_test.dart
@@ -0,0 +1,10 @@
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  test('Tell the user where to find the real tests', () {
+    print('---');
+    print('This package uses integration_test for its tests.');
+    print('See `example/README.md` for more info.');
+    print('---');
+  });
+}