From a5a9b83f3efa21ff42eb02b3f74bc8c08b85d31d Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Wed, 22 May 2024 14:51:10 -0400 Subject: [PATCH] more tests --- .../integration_test/link_widget_test.dart | 201 +++++++++++++++++- 1 file changed, 197 insertions(+), 4 deletions(-) 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 index c247fc930618..a6ee5e5c5187 100644 --- 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 @@ -438,6 +438,197 @@ void main() { // See https://github.com/flutter/flutter/issues/121161 await tester.pumpAndSettle(); }); + + testWidgets('trigger signals are reset after a delay', + (WidgetTester tester) async { + final Uri uri = Uri.parse('/foobar'); + FollowLink? followLinkCallback; + + await tester.pumpWidget(MaterialApp( + routes: { + '/foobar': (BuildContext context) => const Text('Internal route'), + }, + home: WebLinkDelegate(TestLinkInfo( + uri: uri, + target: LinkTarget.blank, + builder: (BuildContext context, FollowLink? followLink) { + followLinkCallback = followLink; + return const SizedBox(width: 100, height: 100); + }, + )), + )); + // Platform view creation happens asynchronously. + await tester.pumpAndSettle(); + + expect(pushedRouteNames, isEmpty); + expect(testPlugin.launches, isEmpty); + + final html.Element anchor = _findSingleAnchor(); + + // A large delay between signals should reset the previous signal. + await followLinkCallback!(); + await Future.delayed(const Duration(seconds: 1)); + final html.Event event1 = _simulateClick(anchor); + + // The link shouldn't have been triggered. + expect(pushedRouteNames, isEmpty); + expect(testPlugin.launches, isEmpty); + expect(event1.defaultPrevented, isFalse); + + await Future.delayed(const Duration(seconds: 1)); + + // Signals with large delay (in reverse order). + final html.Event event2 = _simulateClick(anchor); + await Future.delayed(const Duration(seconds: 1)); + await followLinkCallback!(); + + // The link shouldn't have been triggered. + expect(pushedRouteNames, isEmpty); + expect(testPlugin.launches, isEmpty); + expect(event2.defaultPrevented, isFalse); + + await Future.delayed(const Duration(seconds: 1)); + + // A small delay is okay. + await followLinkCallback!(); + await Future.delayed(const Duration(milliseconds: 100)); + final html.Event event3 = _simulateClick(anchor); + + // The link should've been triggered now. + expect(pushedRouteNames, ['/foobar']); + expect(testPlugin.launches, isEmpty); + expect(event3.defaultPrevented, isTrue); + + // Needed when testing on on Chrome98 headless in CI. + // See https://github.com/flutter/flutter/issues/121161 + await tester.pumpAndSettle(); + }); + + testWidgets('ignores clicks on non-Flutter link', + (WidgetTester tester) async { + final Uri uri = Uri.parse('/foobar'); + FollowLink? followLinkCallback; + + await tester.pumpWidget(MaterialApp( + routes: { + '/foobar': (BuildContext context) => const Text('Internal route'), + }, + home: WebLinkDelegate(TestLinkInfo( + uri: uri, + target: LinkTarget.blank, + builder: (BuildContext context, FollowLink? followLink) { + followLinkCallback = followLink; + return const SizedBox(width: 100, height: 100); + }, + )), + )); + // Platform view creation happens asynchronously. + await tester.pumpAndSettle(); + + expect(pushedRouteNames, isEmpty); + expect(testPlugin.launches, isEmpty); + + final html.Element nonFlutterAnchor = html.document.createElement('a'); + nonFlutterAnchor.setAttribute('href', '/non-flutter'); + + await followLinkCallback!(); + final html.Event event = _simulateClick(nonFlutterAnchor); + + // The link shouldn't have been triggered. + expect(pushedRouteNames, isEmpty); + expect(testPlugin.launches, isEmpty); + expect(event.defaultPrevented, isFalse); + + // Needed when testing on on Chrome98 headless in CI. + // See https://github.com/flutter/flutter/issues/121161 + await tester.pumpAndSettle(); + }); + + testWidgets('handles cmd+click correctly', (WidgetTester tester) async { + final Uri uri = Uri.parse('/foobar'); + FollowLink? followLinkCallback; + + await tester.pumpWidget(MaterialApp( + routes: { + '/foobar': (BuildContext context) => const Text('Internal route'), + }, + home: WebLinkDelegate(TestLinkInfo( + uri: uri, + target: LinkTarget.blank, + builder: (BuildContext context, FollowLink? followLink) { + followLinkCallback = followLink; + return const SizedBox(width: 100, height: 100); + }, + )), + )); + // Platform view creation happens asynchronously. + await tester.pumpAndSettle(); + + expect(pushedRouteNames, isEmpty); + expect(testPlugin.launches, isEmpty); + + final html.Element anchor = _findSingleAnchor(); + + await followLinkCallback!(); + final html.Event event = _simulateClick(anchor, metaKey: true); + + // When a modifier key is present, we should let the browser handle the + // navigation. That means we do nothing on our side. + expect(pushedRouteNames, isEmpty); + expect(testPlugin.launches, isEmpty); + expect(event.defaultPrevented, isFalse); + + // Needed when testing on on Chrome98 headless in CI. + // See https://github.com/flutter/flutter/issues/121161 + await tester.pumpAndSettle(); + }); + + testWidgets('ignores keydown when it is a modifier key', + (WidgetTester tester) async { + final Uri uri = Uri.parse('/foobar'); + FollowLink? followLinkCallback; + + await tester.pumpWidget(MaterialApp( + routes: { + '/foobar': (BuildContext context) => const Text('Internal route'), + }, + home: WebLinkDelegate(TestLinkInfo( + uri: uri, + target: LinkTarget.blank, + builder: (BuildContext context, FollowLink? followLink) { + followLinkCallback = followLink; + return const SizedBox(width: 100, height: 100); + }, + )), + )); + // Platform view creation happens asynchronously. + await tester.pumpAndSettle(); + + expect(pushedRouteNames, isEmpty); + expect(testPlugin.launches, isEmpty); + + final html.Element anchor = _findSingleAnchor(); + + final html.KeyboardEvent event1 = _simulateKeydown(anchor, metaKey: true); + await followLinkCallback!(); + + // When the pressed key is a modifier key, we should ignore it. + expect(pushedRouteNames, isEmpty); + expect(testPlugin.launches, isEmpty); + expect(event1.defaultPrevented, isFalse); + + // If later we receive another trigger, it should work. + final html.KeyboardEvent event2 = _simulateKeydown(anchor); + + // Now the link should be triggered. + expect(pushedRouteNames, ['/foobar']); + expect(testPlugin.launches, isEmpty); + expect(event2.defaultPrevented, isFalse); + + // Needed when testing on on Chrome98 headless in CI. + // See https://github.com/flutter/flutter/issues/121161 + await tester.pumpAndSettle(); + }); }); group('Follows links (reversed order)', () { @@ -641,23 +832,25 @@ html.Element _findSingleAnchor() { return _findAllAnchors().single; } -html.MouseEvent _simulateClick(html.Element target) { +html.MouseEvent _simulateClick(html.Element target, {bool? metaKey}) { final html.MouseEvent mouseEvent = html.MouseEvent( 'click', html.MouseEventInit() ..bubbles = true - ..cancelable = true, + ..cancelable = true + ..metaKey = metaKey ?? false, ); LinkViewController.handleGlobalClick(event: mouseEvent, target: target); return mouseEvent; } -html.KeyboardEvent _simulateKeydown(html.Element target) { +html.KeyboardEvent _simulateKeydown(html.Element target, {bool? metaKey}) { final html.KeyboardEvent keydownEvent = html.KeyboardEvent( 'keydown', html.KeyboardEventInit() ..bubbles = true - ..cancelable = true, + ..cancelable = true + ..metaKey = metaKey ?? false, ); LinkViewController.handleGlobalKeydown(event: keydownEvent); return keydownEvent;