@@ -445,25 +445,23 @@ void main() {
445
445
await followLinkCallback !();
446
446
await Future <void >.delayed (const Duration (seconds: 1 ));
447
447
final html.Event event1 = _simulateClick (anchor);
448
+ await Future <void >.delayed (const Duration (seconds: 1 ));
448
449
449
450
// The link shouldn't have been triggered.
450
451
expect (pushedRouteNames, isEmpty);
451
452
expect (testPlugin.launches, isEmpty);
452
- expect (event1.defaultPrevented, isFalse);
453
-
454
- await Future <void >.delayed (const Duration (seconds: 1 ));
453
+ expect (event1.defaultPrevented, isTrue);
455
454
456
455
// Signals with large delay (in reverse order).
457
456
final html.Event event2 = _simulateClick (anchor);
458
457
await Future <void >.delayed (const Duration (seconds: 1 ));
459
458
await followLinkCallback !();
459
+ await Future <void >.delayed (const Duration (seconds: 1 ));
460
460
461
461
// The link shouldn't have been triggered.
462
462
expect (pushedRouteNames, isEmpty);
463
463
expect (testPlugin.launches, isEmpty);
464
- expect (event2.defaultPrevented, isFalse);
465
-
466
- await Future <void >.delayed (const Duration (seconds: 1 ));
464
+ expect (event2.defaultPrevented, isTrue);
467
465
468
466
// A small delay is okay.
469
467
await followLinkCallback !();
@@ -473,6 +471,7 @@ void main() {
473
471
// The link should've been triggered now.
474
472
expect (pushedRouteNames, < String > ['/foobar' ]);
475
473
expect (testPlugin.launches, isEmpty);
474
+ // (default is prevented here because the link is internal)
476
475
expect (event3.defaultPrevented, isTrue);
477
476
});
478
477
@@ -972,6 +971,109 @@ void main() {
972
971
expect (event.defaultPrevented, isTrue);
973
972
});
974
973
974
+ testWidgets ('handles debounced clicks on semantic link' ,
975
+ (WidgetTester tester) async {
976
+ final Uri uri = Uri .parse ('https://flutter.dev' );
977
+ FollowLink ? followLinkCallback;
978
+
979
+ await tester.pumpWidget (MaterialApp (
980
+ home: WebLinkDelegate (
981
+ semanticsIdentifier: 'test-link-71' ,
982
+ TestLinkInfo (
983
+ uri: uri,
984
+ target: LinkTarget .blank,
985
+ builder: (BuildContext context, FollowLink ? followLink) {
986
+ followLinkCallback = followLink;
987
+ return GestureDetector (
988
+ onTap: () {},
989
+ child: const Text ('My Link' ),
990
+ );
991
+ },
992
+ ),
993
+ ),
994
+ ));
995
+ // Platform view creation happens asynchronously.
996
+ await tester.pumpAndSettle ();
997
+ await tester.pump ();
998
+
999
+ final html.Element semanticsHost =
1000
+ html.document.createElement ('flt-semantics-host' );
1001
+ html.document.body! .append (semanticsHost);
1002
+ final html.Element semanticsAnchor = html.document.createElement ('a' )
1003
+ ..setAttribute ('id' , 'flt-semantic-node-99' )
1004
+ ..setAttribute ('flt-semantics-identifier' , 'test-link-71' )
1005
+ ..setAttribute ('href' , uri.toString ())
1006
+ ..textContent = 'My Text Link' ;
1007
+ semanticsHost.append (semanticsAnchor);
1008
+
1009
+ expect (pushedRouteNames, isEmpty);
1010
+ expect (testPlugin.launches, isEmpty);
1011
+
1012
+ // Receive the DOM click first.
1013
+ final html.Event event = _simulateClick (semanticsAnchor);
1014
+ // The browser will be prevented from navigating right at the end of the event loop because
1015
+ // we are still not sure if the `followLink` will arrive or not.
1016
+ await Future <void >.delayed (Duration .zero);
1017
+ expect (event.defaultPrevented, isTrue);
1018
+ // After some delay, the engine's click debouncer sends the events to the framework and we get
1019
+ // the `followLink` signal.
1020
+ await Future <void >.delayed (const Duration (milliseconds: 200 ));
1021
+ await followLinkCallback !();
1022
+
1023
+ expect (pushedRouteNames, isEmpty);
1024
+ expect (testPlugin.launches, < String > ['https://flutter.dev' ]);
1025
+ });
1026
+
1027
+ // Regression test for: https://github.com/flutter/flutter/issues/162927
1028
+ testWidgets ('prevents navigation with debounced clicks on semantic link' ,
1029
+ (WidgetTester tester) async {
1030
+ final Uri uri = Uri .parse ('https://flutter.dev' );
1031
+
1032
+ await tester.pumpWidget (MaterialApp (
1033
+ home: WebLinkDelegate (
1034
+ semanticsIdentifier: 'test-link-71' ,
1035
+ TestLinkInfo (
1036
+ uri: uri,
1037
+ target: LinkTarget .blank,
1038
+ builder: (BuildContext context, FollowLink ? followLink) {
1039
+ return GestureDetector (
1040
+ onTap: () {},
1041
+ child: const Text ('My Link' ),
1042
+ );
1043
+ },
1044
+ ),
1045
+ ),
1046
+ ));
1047
+ // Platform view creation happens asynchronously.
1048
+ await tester.pumpAndSettle ();
1049
+ await tester.pump ();
1050
+
1051
+ final html.Element semanticsHost =
1052
+ html.document.createElement ('flt-semantics-host' );
1053
+ html.document.body! .append (semanticsHost);
1054
+ final html.Element semanticsAnchor = html.document.createElement ('a' )
1055
+ ..setAttribute ('id' , 'flt-semantic-node-99' )
1056
+ ..setAttribute ('flt-semantics-identifier' , 'test-link-71' )
1057
+ ..setAttribute ('href' , uri.toString ())
1058
+ ..textContent = 'My Text Link' ;
1059
+ semanticsHost.append (semanticsAnchor);
1060
+
1061
+ expect (pushedRouteNames, isEmpty);
1062
+ expect (testPlugin.launches, isEmpty);
1063
+
1064
+ // Receive the DOM click first.
1065
+ final html.Event event = _simulateClick (semanticsAnchor);
1066
+ // The browser will be prevented from navigating right at the end of the event loop because
1067
+ // we are still not sure if the `followLink` will arrive or not.
1068
+ await Future <void >.delayed (Duration .zero);
1069
+ expect (event.defaultPrevented, isTrue);
1070
+
1071
+ // No navigation should occur because no `followLink` signal was received.
1072
+ await Future <void >.delayed (const Duration (seconds: 1 ));
1073
+ expect (pushedRouteNames, isEmpty);
1074
+ expect (testPlugin.launches, isEmpty);
1075
+ });
1076
+
975
1077
// TODO(mdebbar): Remove this test after the engine PR [1] makes it to stable.
976
1078
// [1] https://github.com/flutter/engine/pull/52720
977
1079
testWidgets ('handles clicks on (old) semantic link with a button' ,
0 commit comments