Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -405,11 +405,14 @@ private String maybeGetInitialRouteFromIntent(Intent intent) {
if (host.shouldHandleDeeplinking()) {
Uri data = intent.getData();
if (data != null && !data.getPath().isEmpty()) {
String pathAndQuery = data.getPath();
String fullRoute = data.getPath();
if (data.getQuery() != null && !data.getQuery().isEmpty()) {
pathAndQuery += "?" + data.getQuery();
fullRoute += "?" + data.getQuery();
}
return pathAndQuery;
if (data.getFragment() != null && !data.getFragment().isEmpty()) {
fullRoute += "#" + data.getFragment();
}
return fullRoute;
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,60 @@ public void itForwardsOnRequestPermissionsResultToFlutterEngine() {
.setInitialRoute("/custom/route?query=test");
}

@Test
public void
itSendsInitialRouteFromIntentOnStartIfNoInitialRouteFromActivityAndShouldHandleDeeplinkingWithQueryParameterAndFragment() {
Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application);
intent.setData(Uri.parse("http://myApp/custom/route?query=test#fragment"));

ActivityController<FlutterActivity> activityController =
Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity flutterActivity = activityController.get();

when(mockHost.getActivity()).thenReturn(flutterActivity);
when(mockHost.getInitialRoute()).thenReturn(null);
when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);

// --- Execute the behavior under test ---
// The FlutterEngine is set up in onAttach().
delegate.onAttach(RuntimeEnvironment.application);
// Emulate app start.
delegate.onStart();

// Verify that the navigation channel was given the initial route message.
verify(mockFlutterEngine.getNavigationChannel(), times(1))
.setInitialRoute("/custom/route?query=test#fragment");
}

@Test
public void
itSendsInitialRouteFromIntentOnStartIfNoInitialRouteFromActivityAndShouldHandleDeeplinkingWithFragmentNoQueryParameter() {
Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application);
intent.setData(Uri.parse("http://myApp/custom/route#fragment"));

ActivityController<FlutterActivity> activityController =
Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity flutterActivity = activityController.get();

when(mockHost.getActivity()).thenReturn(flutterActivity);
when(mockHost.getInitialRoute()).thenReturn(null);
when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);

// --- Execute the behavior under test ---
// The FlutterEngine is set up in onAttach().
delegate.onAttach(RuntimeEnvironment.application);
// Emulate app start.
delegate.onStart();

// Verify that the navigation channel was given the initial route message.
verify(mockFlutterEngine.getNavigationChannel(), times(1))
.setInitialRoute("/custom/route#fragment");
}

@Test
public void
itSendsInitialRouteFromIntentOnStartIfNoInitialRouteFromActivityAndShouldHandleDeeplinkingNoQueryParameter() {
Expand Down Expand Up @@ -527,6 +581,46 @@ public void itSendsPushRouteMessageWhenOnNewIntent() {
.pushRoute("/custom/route?query=test");
}

@Test
public void itSendsPushRouteMessageWhenOnNewIntentWithQueryParameterAndFragment() {
when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);

// --- Execute the behavior under test ---
// The FlutterEngine is set up in onAttach().
delegate.onAttach(RuntimeEnvironment.application);

Intent mockIntent = mock(Intent.class);
when(mockIntent.getData())
.thenReturn(Uri.parse("http://myApp/custom/route?query=test#fragment"));
// Emulate the host and call the method that we expect to be forwarded.
delegate.onNewIntent(mockIntent);

// Verify that the navigation channel was given the push route message.
verify(mockFlutterEngine.getNavigationChannel(), times(1))
.pushRoute("/custom/route?query=test#fragment");
}

@Test
public void itSendsPushRouteMessageWhenOnNewIntentWithFragmentNoQueryParameter() {
when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);

// --- Execute the behavior under test ---
// The FlutterEngine is set up in onAttach().
delegate.onAttach(RuntimeEnvironment.application);

Intent mockIntent = mock(Intent.class);
when(mockIntent.getData()).thenReturn(Uri.parse("http://myApp/custom/route#fragment"));
// Emulate the host and call the method that we expect to be forwarded.
delegate.onNewIntent(mockIntent);

// Verify that the navigation channel was given the push route message.
verify(mockFlutterEngine.getNavigationChannel(), times(1)).pushRoute("/custom/route#fragment");
}

@Test
public void itSendsPushRouteMessageWhenOnNewIntentNoQueryParameter() {
when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
Expand Down
10 changes: 6 additions & 4 deletions shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,15 @@ - (BOOL)application:(UIApplication*)application
FML_LOG(ERROR)
<< "Timeout waiting for the first frame when launching an URL.";
} else {
NSString* pathAndQuery = url.path;
NSString* fullRoute = url.path;
if ([url.query length] != 0) {
pathAndQuery =
[NSString stringWithFormat:@"%@?%@", pathAndQuery, url.query];
fullRoute = [NSString stringWithFormat:@"%@?%@", fullRoute, url.query];
}
if ([url.fragment length] != 0) {
fullRoute = [NSString stringWithFormat:@"%@#%@", fullRoute, url.fragment];
}
[flutterViewController.engine.navigationChannel invokeMethod:@"pushRoute"
arguments:pathAndQuery];
arguments:fullRoute];
}
}];
return YES;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,51 @@ - (void)skip_testLaunchUrl {
OCMVerify([navigationChannel invokeMethod:@"pushRoute" arguments:@"/custom/route?query=test"]);
}

- (void)skip_testLaunchUrlWithQueryParameterAndFragment {
FlutterAppDelegate* appDelegate = [[FlutterAppDelegate alloc] init];
FlutterViewController* viewController = OCMClassMock([FlutterViewController class]);
FlutterEngine* engine = OCMClassMock([FlutterEngine class]);
FlutterMethodChannel* navigationChannel = OCMClassMock([FlutterMethodChannel class]);
OCMStub([engine navigationChannel]).andReturn(navigationChannel);
OCMStub([viewController engine]).andReturn(engine);
OCMStub([engine waitForFirstFrame:3.0 callback:([OCMArg invokeBlockWithArgs:@(NO), nil])]);
appDelegate.rootFlutterViewControllerGetter = ^{
return viewController;
};
NSURL* url = [NSURL URLWithString:@"http://myApp/custom/route?query=test#fragment"];
NSDictionary<UIApplicationOpenURLOptionsKey, id>* options = @{};
BOOL result = [appDelegate application:[UIApplication sharedApplication]
openURL:url
options:options
infoPlistGetter:^NSDictionary*() {
return @{@"FlutterDeepLinkingEnabled" : @(YES)};
}];
XCTAssertTrue(result);
OCMVerify([navigationChannel invokeMethod:@"pushRoute"
arguments:@"/custom/route?query=test#fragment"]);
}

- (void)skip_testLaunchUrlWithFragmentNoQueryParameter {
FlutterAppDelegate* appDelegate = [[FlutterAppDelegate alloc] init];
FlutterViewController* viewController = OCMClassMock([FlutterViewController class]);
FlutterEngine* engine = OCMClassMock([FlutterEngine class]);
FlutterMethodChannel* navigationChannel = OCMClassMock([FlutterMethodChannel class]);
OCMStub([engine navigationChannel]).andReturn(navigationChannel);
OCMStub([viewController engine]).andReturn(engine);
OCMStub([engine waitForFirstFrame:3.0 callback:([OCMArg invokeBlockWithArgs:@(NO), nil])]);
appDelegate.rootFlutterViewControllerGetter = ^{
return viewController;
};
NSURL* url = [NSURL URLWithString:@"http://myApp/custom/route#fragment"];
NSDictionary<UIApplicationOpenURLOptionsKey, id>* options = @{};
BOOL result = [appDelegate application:[UIApplication sharedApplication]
openURL:url
options:options
infoPlistGetter:^NSDictionary*() {
return @{@"FlutterDeepLinkingEnabled" : @(YES)};
}];
XCTAssertTrue(result);
OCMVerify([navigationChannel invokeMethod:@"pushRoute" arguments:@"/custom/route#fragment"]);
}

@end