mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Remove Robolectric.setupActivity from engine junit tests (#167607)
Remove the deprecated Robolectric.setupActivity from the engine junit tests. This replaces it with the ActivityScenario. Activity Scenario documentation https://developer.android.com/reference/androidx/test/core/app/ActivityScenario Extension function that sets up and shuts down an activity before and after each test. https://developer.android.com/reference/androidx/test/ext/junit/rules/ActivityScenarioRule Fixes #133151 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing.
This commit is contained in:
parent
f5ae49a862
commit
3438da3cf4
@ -29,6 +29,7 @@ import android.view.View;
|
||||
import android.window.BackEvent;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.FlutterInjector;
|
||||
@ -73,9 +74,6 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
private FlutterActivityAndFragmentDelegate.Host mockHost;
|
||||
private FlutterActivityAndFragmentDelegate.Host mockHost2;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// Robolectric.setupActivity
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
@Before
|
||||
public void setup() {
|
||||
FlutterInjector.reset();
|
||||
@ -86,7 +84,6 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
// Create a mocked Host, which is required by the delegate being tested.
|
||||
mockHost = mock(FlutterActivityAndFragmentDelegate.Host.class);
|
||||
when(mockHost.getContext()).thenReturn(ctx);
|
||||
when(mockHost.getActivity()).thenReturn(Robolectric.setupActivity(Activity.class));
|
||||
when(mockHost.getLifecycle()).thenReturn(mock(Lifecycle.class));
|
||||
when(mockHost.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
|
||||
when(mockHost.getDartEntrypointFunctionName()).thenReturn("main");
|
||||
@ -104,7 +101,6 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
|
||||
mockHost2 = mock(FlutterActivityAndFragmentDelegate.Host.class);
|
||||
when(mockHost2.getContext()).thenReturn(ctx);
|
||||
when(mockHost2.getActivity()).thenReturn(Robolectric.setupActivity(Activity.class));
|
||||
when(mockHost2.getLifecycle()).thenReturn(mock(Lifecycle.class));
|
||||
when(mockHost2.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
|
||||
when(mockHost2.getDartEntrypointFunctionName()).thenReturn("main");
|
||||
@ -126,79 +122,90 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
// ---- Test setup ----
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
// We're testing lifecycle behaviors, which require/expect that certain methods have already
|
||||
// been executed by the time they run. Therefore, we run those expected methods first.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
// We're testing lifecycle behaviors, which require/expect that certain methods have
|
||||
// already
|
||||
// been executed by the time they run. Therefore, we run those expected methods first.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// By the time an Activity/Fragment is started, we don't expect any lifecycle messages
|
||||
// to have been sent to Flutter.
|
||||
delegate.onStart();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
// --- Execute the behavior under test ---
|
||||
// By the time an Activity/Fragment is started, we don't expect any lifecycle messages
|
||||
// to have been sent to Flutter.
|
||||
delegate.onStart();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
|
||||
// When the Activity/Fragment is resumed, a resumed message should have been sent to Flutter.
|
||||
delegate.onResume();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
// When the Activity/Fragment is resumed, a resumed message should have been sent to
|
||||
// Flutter.
|
||||
delegate.onResume();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
|
||||
// When the app loses focus because something else has it (e.g. notification
|
||||
// windowshade or app switcher), it should go to inactive.
|
||||
delegate.onWindowFocusChanged(false);
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
// When the app loses focus because something else has it (e.g. notification
|
||||
// windowshade or app switcher), it should go to inactive.
|
||||
delegate.onWindowFocusChanged(false);
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
|
||||
// When the app regains focus, it should go to resumed again.
|
||||
delegate.onWindowFocusChanged(true);
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
// When the app regains focus, it should go to resumed again.
|
||||
delegate.onWindowFocusChanged(true);
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
|
||||
// When the Activity/Fragment is paused, an inactive message should have been sent to Flutter.
|
||||
delegate.onPause();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
// When the Activity/Fragment is paused, an inactive message should have been sent to
|
||||
// Flutter.
|
||||
delegate.onPause();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
|
||||
// When the Activity/Fragment is stopped, a paused message should have been sent to Flutter.
|
||||
// Notice that Flutter uses the term "paused" in a different way, and at a different time
|
||||
// than the Android OS.
|
||||
delegate.onStop();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
// When the Activity/Fragment is stopped, a paused message should have been sent to
|
||||
// Flutter.
|
||||
// Notice that Flutter uses the term "paused" in a different way, and at a different
|
||||
// time
|
||||
// than the Android OS.
|
||||
delegate.onStop();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
|
||||
// When activity detaches, a detached message should have been sent to Flutter.
|
||||
delegate.onDetach();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsDetached();
|
||||
// When activity detaches, a detached message should have been sent to Flutter.
|
||||
delegate.onDetach();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsDetached();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -206,27 +213,34 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
// ---- Test setup ----
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
when(mockHost.shouldDispatchAppLifecycleState()).thenReturn(false);
|
||||
when(mockHost.shouldDispatchAppLifecycleState()).thenReturn(false);
|
||||
|
||||
// We're testing lifecycle behaviors, which require/expect that certain methods have already
|
||||
// been executed by the time they run. Therefore, we run those expected methods first.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onWindowFocusChanged(false);
|
||||
delegate.onWindowFocusChanged(true);
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDetach();
|
||||
// We're testing lifecycle behaviors, which require/expect that certain methods have
|
||||
// already
|
||||
// been executed by the time they run. Therefore, we run those expected methods first.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onWindowFocusChanged(false);
|
||||
delegate.onWindowFocusChanged(true);
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDetach();
|
||||
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -291,12 +305,19 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// The FlutterEngine existence is verified in onAttach()
|
||||
delegate.onAttach(ctx);
|
||||
|
||||
// Expect IllegalStateException.
|
||||
// --- Execute the behavior under test ---
|
||||
// The FlutterEngine existence is verified in onAttach()
|
||||
delegate.onAttach(ctx);
|
||||
});
|
||||
} catch (IllegalStateException e) {
|
||||
// Expect IllegalStateException.
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Bug: b/271100292
|
||||
@ -407,13 +428,19 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
// ---- Test setup ----
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// The FlutterEngine is created in onAttach().
|
||||
delegate.onAttach(ctx);
|
||||
// --- Execute the behavior under test ---
|
||||
// The FlutterEngine is created in onAttach().
|
||||
delegate.onAttach(ctx);
|
||||
|
||||
// Verify that the host was asked to configure our FlutterEngine.
|
||||
verify(mockHost, times(1)).configureFlutterEngine(mockFlutterEngine);
|
||||
// Verify that the host was asked to configure our FlutterEngine.
|
||||
verify(mockHost, times(1)).configureFlutterEngine(mockFlutterEngine);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -430,36 +457,40 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
verify(mockHost, times(1)).onFlutterSurfaceViewCreated(isNotNull());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// Robolectric.setupActivity
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
@Test
|
||||
public void itGivesHostAnOpportunityToConfigureFlutterTextureView() {
|
||||
// ---- Test setup ----
|
||||
Host customMockHost = mock(Host.class);
|
||||
when(customMockHost.getContext()).thenReturn(ctx);
|
||||
when(customMockHost.getActivity()).thenReturn(Robolectric.setupActivity(Activity.class));
|
||||
when(customMockHost.getLifecycle()).thenReturn(mock(Lifecycle.class));
|
||||
when(customMockHost.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
|
||||
when(customMockHost.getDartEntrypointFunctionName()).thenReturn("main");
|
||||
when(customMockHost.getAppBundlePath()).thenReturn("/fake/path");
|
||||
when(customMockHost.getInitialRoute()).thenReturn("/");
|
||||
when(customMockHost.getRenderMode()).thenReturn(RenderMode.texture);
|
||||
when(customMockHost.getTransparencyMode()).thenReturn(TransparencyMode.transparent);
|
||||
when(customMockHost.provideFlutterEngine(any(Context.class))).thenReturn(mockFlutterEngine);
|
||||
when(customMockHost.shouldAttachEngineToActivity()).thenReturn(true);
|
||||
when(customMockHost.shouldDestroyEngineWithHost()).thenReturn(true);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(customMockHost.getActivity()).thenReturn(activity);
|
||||
when(customMockHost.getLifecycle()).thenReturn(mock(Lifecycle.class));
|
||||
when(customMockHost.getFlutterShellArgs())
|
||||
.thenReturn(new FlutterShellArgs(new String[] {}));
|
||||
when(customMockHost.getDartEntrypointFunctionName()).thenReturn("main");
|
||||
when(customMockHost.getAppBundlePath()).thenReturn("/fake/path");
|
||||
when(customMockHost.getInitialRoute()).thenReturn("/");
|
||||
when(customMockHost.getRenderMode()).thenReturn(RenderMode.texture);
|
||||
when(customMockHost.getTransparencyMode()).thenReturn(TransparencyMode.transparent);
|
||||
when(customMockHost.provideFlutterEngine(any(Context.class)))
|
||||
.thenReturn(mockFlutterEngine);
|
||||
when(customMockHost.shouldAttachEngineToActivity()).thenReturn(true);
|
||||
when(customMockHost.shouldDestroyEngineWithHost()).thenReturn(true);
|
||||
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate =
|
||||
new FlutterActivityAndFragmentDelegate(customMockHost);
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate =
|
||||
new FlutterActivityAndFragmentDelegate(customMockHost);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, false);
|
||||
// --- Execute the behavior under test ---
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, false);
|
||||
|
||||
// Verify that the host was asked to configure a FlutterTextureView.
|
||||
verify(customMockHost, times(1)).onFlutterTextureViewCreated(isNotNull());
|
||||
// Verify that the host was asked to configure a FlutterTextureView.
|
||||
verify(customMockHost, times(1)).onFlutterTextureViewCreated(isNotNull());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -467,14 +498,20 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
// ---- Test setup ----
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// The FlutterEngine is created in onAttach().
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onDetach();
|
||||
// --- Execute the behavior under test ---
|
||||
// The FlutterEngine is created in onAttach().
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onDetach();
|
||||
|
||||
// Verify that the host was asked to configure our FlutterEngine.
|
||||
verify(mockHost, times(1)).cleanUpFlutterEngine(mockFlutterEngine);
|
||||
// Verify that the host was asked to configure our FlutterEngine.
|
||||
verify(mockHost, times(1)).cleanUpFlutterEngine(mockFlutterEngine);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -608,20 +645,26 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// Flutter is attached to the surrounding Activity in onAttach.
|
||||
delegate.onAttach(ctx);
|
||||
// --- Execute the behavior under test ---
|
||||
// Flutter is attached to the surrounding Activity in onAttach.
|
||||
delegate.onAttach(ctx);
|
||||
|
||||
// Verify that the ActivityControlSurface was told to attach to an Activity.
|
||||
verify(mockFlutterEngine.getActivityControlSurface(), times(1))
|
||||
.attachToActivity(any(ExclusiveAppComponent.class), any(Lifecycle.class));
|
||||
// Verify that the ActivityControlSurface was told to attach to an Activity.
|
||||
verify(mockFlutterEngine.getActivityControlSurface(), times(1))
|
||||
.attachToActivity(any(ExclusiveAppComponent.class), any(Lifecycle.class));
|
||||
|
||||
// Flutter is detached from the surrounding Activity in onDetach.
|
||||
delegate.onDetach();
|
||||
// Flutter is detached from the surrounding Activity in onDetach.
|
||||
delegate.onDetach();
|
||||
|
||||
// Verify that the ActivityControlSurface was told to detach from the Activity.
|
||||
verify(mockFlutterEngine.getActivityControlSurface(), times(1)).detachFromActivity();
|
||||
// Verify that the ActivityControlSurface was told to detach from the Activity.
|
||||
verify(mockFlutterEngine.getActivityControlSurface(), times(1)).detachFromActivity();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// "Attaching" to the surrounding Activity refers to Flutter being able to control
|
||||
@ -1114,20 +1157,26 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
|
||||
// --- Verify that the cached engine was destroyed ---
|
||||
verify(mockFlutterEngine, times(1)).destroy();
|
||||
// --- Verify that the cached engine was destroyed ---
|
||||
verify(mockFlutterEngine, times(1)).destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1138,20 +1187,26 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
|
||||
// --- Verify that the cached engine was destroyed ---
|
||||
verify(mockFlutterEngine, never()).destroy();
|
||||
// --- Verify that the cached engine was destroyed ---
|
||||
verify(mockFlutterEngine, never()).destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1169,21 +1224,27 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
|
||||
// --- Verify that the cached engine was destroyed ---
|
||||
verify(cachedEngine, times(1)).destroy();
|
||||
assertNull(FlutterEngineCache.getInstance().get("my_flutter_engine"));
|
||||
// --- Verify that the cached engine was destroyed ---
|
||||
verify(cachedEngine, times(1)).destroy();
|
||||
assertNull(FlutterEngineCache.getInstance().get("my_flutter_engine"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1201,20 +1262,26 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
|
||||
// --- Verify that the cached engine was NOT destroyed ---
|
||||
verify(cachedEngine, never()).destroy();
|
||||
// --- Verify that the cached engine was NOT destroyed ---
|
||||
verify(cachedEngine, never()).destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1385,55 +1452,75 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
|
||||
// Create the real objects that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
|
||||
FlutterActivityAndFragmentDelegate delegate2 =
|
||||
new FlutterActivityAndFragmentDelegate(mockHost2);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
when(mockHost.getActivity()).thenReturn(activity);
|
||||
|
||||
// This test is written to recreate the following scenario:
|
||||
// 1. We have a FlutterFragment_A attached to a singleton cached engine.
|
||||
// 2. An intent arrives that spawns FlutterFragment_B.
|
||||
// 3. FlutterFragment_B starts and steals the engine from FlutterFragment_A while attaching.
|
||||
// Via a call to FlutterActivityAndFragmentDelegate.detachFromFlutterEngine().
|
||||
// 4. FlutterFragment_A is forcibly detached from the engine.
|
||||
// 5. FlutterFragment_B is attached to the engine.
|
||||
// 6. FlutterFragment_A is detached from the engine.
|
||||
// Note that the second detach for FlutterFragment_A is done unconditionally when the Fragment
|
||||
// is being
|
||||
// torn down.
|
||||
FlutterActivityAndFragmentDelegate delegate2 =
|
||||
new FlutterActivityAndFragmentDelegate(mockHost2);
|
||||
try (ActivityScenario<Activity> scenario2 = ActivityScenario.launch(Activity.class)) {
|
||||
scenario2.onActivity(
|
||||
activity2 -> {
|
||||
when(mockHost2.getActivity()).thenReturn(activity2);
|
||||
|
||||
// At this point the engine's life cycle channel receives a message (triggered by
|
||||
// FlutterFragment_A's second detach)
|
||||
// that indicates the app is detached. This breaks FlutterFragment_B.
|
||||
// This test is written to recreate the following scenario:
|
||||
// 1. We have a FlutterFragment_A attached to a singleton cached engine.
|
||||
// 2. An intent arrives that spawns FlutterFragment_B.
|
||||
// 3. FlutterFragment_B starts and steals the engine from FlutterFragment_A
|
||||
// while attaching.
|
||||
// Via a call to
|
||||
// FlutterActivityAndFragmentDelegate.detachFromFlutterEngine().
|
||||
// 4. FlutterFragment_A is forcibly detached from the engine.
|
||||
// 5. FlutterFragment_B is attached to the engine.
|
||||
// 6. FlutterFragment_A is detached from the engine.
|
||||
// Note that the second detach for FlutterFragment_A is done unconditionally
|
||||
// when the Fragment
|
||||
// is being
|
||||
// torn down.
|
||||
|
||||
// Below is a sequence of calls that mimicks the calls that the above scenario would trigger
|
||||
// without
|
||||
// relying on an intent to trigger the behaviour.
|
||||
// At this point the engine's life cycle channel receives a message (triggered
|
||||
// by
|
||||
// FlutterFragment_A's second detach)
|
||||
// that indicates the app is detached. This breaks FlutterFragment_B.
|
||||
|
||||
// FlutterFragment_A is attached to the engine.
|
||||
delegate.onAttach(ctx);
|
||||
// Below is a sequence of calls that mimicks the calls that the above scenario
|
||||
// would trigger
|
||||
// without
|
||||
// relying on an intent to trigger the behaviour.
|
||||
|
||||
// NOTE: The following two calls happen in a slightly different order in reality. That is, via,
|
||||
// a call to host.detachFromFlutterEngine, delegate2.onAttach ends up invoking
|
||||
// delegate.onDetach.
|
||||
// To keep this regression test simple, we call them directly.
|
||||
// FlutterFragment_A is attached to the engine.
|
||||
delegate.onAttach(ctx);
|
||||
|
||||
// Detach FlutterFragment_A.
|
||||
delegate.onDetach();
|
||||
// NOTE: The following two calls happen in a slightly different order in
|
||||
// reality. That is, via,
|
||||
// a call to host.detachFromFlutterEngine, delegate2.onAttach ends up invoking
|
||||
// delegate.onDetach.
|
||||
// To keep this regression test simple, we call them directly.
|
||||
|
||||
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
|
||||
// Detach FlutterFragment_A.
|
||||
delegate.onDetach();
|
||||
|
||||
// Attaches to the engine FlutterFragment_B.
|
||||
delegate2.onAttach(ctx);
|
||||
delegate2.onResume();
|
||||
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
|
||||
|
||||
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
|
||||
// Attaches to the engine FlutterFragment_B.
|
||||
delegate2.onAttach(ctx);
|
||||
delegate2.onResume();
|
||||
|
||||
// A second Detach of FlutterFragment_A happens when the Fragment is detached.
|
||||
delegate.onDetach();
|
||||
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
|
||||
|
||||
// IMPORTANT: The bug we fixed would have resulted in the engine thinking the app
|
||||
// is detached twice instead of once.
|
||||
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
|
||||
// A second Detach of FlutterFragment_A happens when the Fragment is detached.
|
||||
delegate.onDetach();
|
||||
|
||||
// IMPORTANT: The bug we fixed would have resulted in the engine thinking the
|
||||
// app
|
||||
// is detached twice instead of once.
|
||||
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -25,6 +25,7 @@ import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
@ -66,43 +67,49 @@ public class FlutterAndroidComponentTest {
|
||||
cachedEngine.getPlugins().add(mockPlugin);
|
||||
|
||||
// Create a fake Host, which is required by the delegate.
|
||||
FakeHost fakeHost = new FakeHost(cachedEngine);
|
||||
fakeHost.shouldDestroyEngineWithHost = true;
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
FakeHost fakeHost = new FakeHost(cachedEngine, activity);
|
||||
fakeHost.shouldDestroyEngineWithHost = true;
|
||||
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(fakeHost);
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate =
|
||||
new FlutterActivityAndFragmentDelegate(fakeHost);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
|
||||
// Verify that the plugin is attached to the FlutterEngine.
|
||||
ArgumentCaptor<FlutterPlugin.FlutterPluginBinding> pluginBindingCaptor =
|
||||
ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
|
||||
verify(mockPlugin, times(1)).onAttachedToEngine(pluginBindingCaptor.capture());
|
||||
FlutterPlugin.FlutterPluginBinding binding = pluginBindingCaptor.getValue();
|
||||
assertNotNull(binding.getApplicationContext());
|
||||
assertNotNull(binding.getBinaryMessenger());
|
||||
assertNotNull(binding.getTextureRegistry());
|
||||
assertNotNull(binding.getPlatformViewRegistry());
|
||||
// Verify that the plugin is attached to the FlutterEngine.
|
||||
ArgumentCaptor<FlutterPlugin.FlutterPluginBinding> pluginBindingCaptor =
|
||||
ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
|
||||
verify(mockPlugin, times(1)).onAttachedToEngine(pluginBindingCaptor.capture());
|
||||
FlutterPlugin.FlutterPluginBinding binding = pluginBindingCaptor.getValue();
|
||||
assertNotNull(binding.getApplicationContext());
|
||||
assertNotNull(binding.getBinaryMessenger());
|
||||
assertNotNull(binding.getTextureRegistry());
|
||||
assertNotNull(binding.getPlatformViewRegistry());
|
||||
|
||||
delegate.onRestoreInstanceState(null);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
delegate.onRestoreInstanceState(null);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
|
||||
// Verify the plugin was detached from the FlutterEngine.
|
||||
pluginBindingCaptor = ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
|
||||
verify(mockPlugin, times(1)).onDetachedFromEngine(pluginBindingCaptor.capture());
|
||||
binding = pluginBindingCaptor.getValue();
|
||||
assertNotNull(binding.getApplicationContext());
|
||||
assertNotNull(binding.getBinaryMessenger());
|
||||
assertNotNull(binding.getTextureRegistry());
|
||||
assertNotNull(binding.getPlatformViewRegistry());
|
||||
// Verify the plugin was detached from the FlutterEngine.
|
||||
pluginBindingCaptor = ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
|
||||
verify(mockPlugin, times(1)).onDetachedFromEngine(pluginBindingCaptor.capture());
|
||||
binding = pluginBindingCaptor.getValue();
|
||||
assertNotNull(binding.getApplicationContext());
|
||||
assertNotNull(binding.getBinaryMessenger());
|
||||
assertNotNull(binding.getTextureRegistry());
|
||||
assertNotNull(binding.getPlatformViewRegistry());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -139,42 +146,49 @@ public class FlutterAndroidComponentTest {
|
||||
cachedEngine.getPlugins().add(mockPlugin);
|
||||
|
||||
// Create a fake Host, which is required by the delegate.
|
||||
FlutterActivityAndFragmentDelegate.Host fakeHost = new FakeHost(cachedEngine);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
FlutterActivityAndFragmentDelegate.Host fakeHost = new FakeHost(cachedEngine, activity);
|
||||
|
||||
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(fakeHost);
|
||||
FlutterActivityAndFragmentDelegate delegate =
|
||||
new FlutterActivityAndFragmentDelegate(fakeHost);
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
|
||||
// Verify plugin was given an ActivityPluginBinding.
|
||||
ArgumentCaptor<ActivityPluginBinding> pluginBindingCaptor =
|
||||
ArgumentCaptor.forClass(ActivityPluginBinding.class);
|
||||
verify(activityAwarePlugin, times(1)).onAttachedToActivity(pluginBindingCaptor.capture());
|
||||
ActivityPluginBinding binding = pluginBindingCaptor.getValue();
|
||||
assertNotNull(binding.getActivity());
|
||||
assertNotNull(binding.getLifecycle());
|
||||
// Verify plugin was given an ActivityPluginBinding.
|
||||
ArgumentCaptor<ActivityPluginBinding> pluginBindingCaptor =
|
||||
ArgumentCaptor.forClass(ActivityPluginBinding.class);
|
||||
verify(activityAwarePlugin, times(1))
|
||||
.onAttachedToActivity(pluginBindingCaptor.capture());
|
||||
ActivityPluginBinding binding = pluginBindingCaptor.getValue();
|
||||
assertNotNull(binding.getActivity());
|
||||
assertNotNull(binding.getLifecycle());
|
||||
|
||||
delegate.onRestoreInstanceState(null);
|
||||
delegate.onRestoreInstanceState(null);
|
||||
|
||||
// Verify that after Activity creation, the plugin was allowed to restore state.
|
||||
verify(mockSaveStateListener, times(1)).onRestoreInstanceState(isNull());
|
||||
// Verify that after Activity creation, the plugin was allowed to restore state.
|
||||
verify(mockSaveStateListener, times(1)).onRestoreInstanceState(isNull());
|
||||
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onSaveInstanceState(mock(Bundle.class));
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onSaveInstanceState(mock(Bundle.class));
|
||||
|
||||
// Verify that the plugin was allowed to save state.
|
||||
verify(mockSaveStateListener, times(1)).onSaveInstanceState(any(Bundle.class));
|
||||
// Verify that the plugin was allowed to save state.
|
||||
verify(mockSaveStateListener, times(1)).onSaveInstanceState(any(Bundle.class));
|
||||
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
|
||||
// Verify that the plugin was detached from the Activity.
|
||||
verify(activityAwarePlugin, times(1)).onDetachedFromActivity();
|
||||
// Verify that the plugin was detached from the Activity.
|
||||
verify(activityAwarePlugin, times(1)).onDetachedFromActivity();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -188,25 +202,30 @@ public class FlutterAndroidComponentTest {
|
||||
FlutterEngineCache.getInstance().put("my_flutter_engine", cachedEngine);
|
||||
|
||||
// Create a fake Host, which is required by the delegate.
|
||||
FakeHost fakeHost = new FakeHost(cachedEngine);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
FakeHost fakeHost = new FakeHost(cachedEngine, activity);
|
||||
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate =
|
||||
spy(new FlutterActivityAndFragmentDelegate(fakeHost));
|
||||
// Create the real object that we're testing.
|
||||
FlutterActivityAndFragmentDelegate delegate =
|
||||
spy(new FlutterActivityAndFragmentDelegate(fakeHost));
|
||||
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onRestoreInstanceState(null);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
// --- Execute the behavior under test ---
|
||||
// Push the delegate through all lifecycle methods all the way to destruction.
|
||||
delegate.onAttach(ctx);
|
||||
delegate.onRestoreInstanceState(null);
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDestroyView();
|
||||
delegate.onDetach();
|
||||
|
||||
verify(delegate, never()).detachFromFlutterEngine();
|
||||
verify(delegate, never()).detachFromFlutterEngine();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -253,12 +272,13 @@ public class FlutterAndroidComponentTest {
|
||||
|
||||
private static class FakeHost implements FlutterActivityAndFragmentDelegate.Host {
|
||||
final FlutterEngine cachedEngine;
|
||||
Activity activity;
|
||||
Activity cachedActivity;
|
||||
boolean shouldDestroyEngineWithHost = false;
|
||||
Lifecycle lifecycle = mock(Lifecycle.class);
|
||||
|
||||
private FakeHost(@NonNull FlutterEngine flutterEngine) {
|
||||
private FakeHost(@NonNull FlutterEngine flutterEngine, @NonNull Activity activity) {
|
||||
cachedEngine = flutterEngine;
|
||||
cachedActivity = activity;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -267,16 +287,10 @@ public class FlutterAndroidComponentTest {
|
||||
return ApplicationProvider.getApplicationContext();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// Robolectric.setupActivity
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
@Nullable
|
||||
@NonNull
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
if (activity == null) {
|
||||
activity = Robolectric.setupActivity(Activity.class);
|
||||
}
|
||||
return activity;
|
||||
return cachedActivity;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@ -22,6 +22,7 @@ import static org.mockito.Mockito.when;
|
||||
import android.content.Context;
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
@ -33,7 +34,6 @@ import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class FlutterFragmentTest {
|
||||
@ -285,12 +285,6 @@ public class FlutterFragmentTest {
|
||||
assertEquals(fragment.getExclusiveAppComponent(), delegate);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private FragmentActivity getMockFragmentActivity() {
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
return Robolectric.setupActivity(FragmentActivity.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void itDelegatesOnBackPressedWithSetFrameworkHandlesBack() {
|
||||
// We need to mock FlutterJNI to avoid triggering native code.
|
||||
@ -307,36 +301,39 @@ public class FlutterFragmentTest {
|
||||
// sends backs to the framework if setFrameworkHandlesBack is true.
|
||||
.shouldAutomaticallyHandleOnBackPressed(true)
|
||||
.build();
|
||||
FragmentActivity activity = getMockFragmentActivity();
|
||||
activity
|
||||
.getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(android.R.id.content, fragment)
|
||||
.commitNow();
|
||||
|
||||
FlutterActivityAndFragmentDelegate mockDelegate =
|
||||
mock(FlutterActivityAndFragmentDelegate.class);
|
||||
isDelegateAttached = true;
|
||||
when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
|
||||
doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
|
||||
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
|
||||
fragment.setDelegateFactory(delegateFactory);
|
||||
try (ActivityScenario<FragmentActivity> scenario =
|
||||
ActivityScenario.launch(FragmentActivity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
activity
|
||||
.getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(android.R.id.content, fragment)
|
||||
.commitNow();
|
||||
|
||||
// Calling onBackPressed now will still be handled by Android (the default),
|
||||
// until setFrameworkHandlesBack is set to true.
|
||||
activity.getOnBackPressedDispatcher().onBackPressed();
|
||||
verify(mockDelegate, times(0)).onBackPressed();
|
||||
FlutterActivityAndFragmentDelegate mockDelegate =
|
||||
mock(FlutterActivityAndFragmentDelegate.class);
|
||||
isDelegateAttached = true;
|
||||
when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
|
||||
doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
|
||||
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
|
||||
fragment.setDelegateFactory(delegateFactory);
|
||||
|
||||
// Setting setFrameworkHandlesBack to true means the delegate will receive
|
||||
// the back and Android won't handle it.
|
||||
fragment.setFrameworkHandlesBack(true);
|
||||
activity.getOnBackPressedDispatcher().onBackPressed();
|
||||
verify(mockDelegate, times(1)).onBackPressed();
|
||||
// Calling onBackPressed now will still be handled by Android (the default),
|
||||
// until setFrameworkHandlesBack is set to true.
|
||||
activity.getOnBackPressedDispatcher().onBackPressed();
|
||||
verify(mockDelegate, times(0)).onBackPressed();
|
||||
|
||||
// Setting setFrameworkHandlesBack to true means the delegate will receive
|
||||
// the back and Android won't handle it.
|
||||
fragment.setFrameworkHandlesBack(true);
|
||||
activity.getOnBackPressedDispatcher().onBackPressed();
|
||||
verify(mockDelegate, times(1)).onBackPressed();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// Robolectric.setupActivity
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
@Test
|
||||
public void itHandlesPopSystemNavigationAutomaticallyWhenEnabled() {
|
||||
// We need to mock FlutterJNI to avoid triggering native code.
|
||||
@ -351,41 +348,47 @@ public class FlutterFragmentTest {
|
||||
FlutterFragment.withCachedEngine("my_cached_engine")
|
||||
.shouldAutomaticallyHandleOnBackPressed(true)
|
||||
.build();
|
||||
FragmentActivity activity = getMockFragmentActivity();
|
||||
activity
|
||||
.getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(android.R.id.content, fragment)
|
||||
.commitNow();
|
||||
final AtomicBoolean onBackPressedCalled = new AtomicBoolean(false);
|
||||
OnBackPressedCallback callback =
|
||||
new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
onBackPressedCalled.set(true);
|
||||
}
|
||||
};
|
||||
activity.getOnBackPressedDispatcher().addCallback(callback);
|
||||
|
||||
FlutterActivityAndFragmentDelegate mockDelegate =
|
||||
mock(FlutterActivityAndFragmentDelegate.class);
|
||||
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
|
||||
fragment.setDelegateFactory(delegateFactory);
|
||||
try (ActivityScenario<FragmentActivity> scenario =
|
||||
ActivityScenario.launch(FragmentActivity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
activity
|
||||
.getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(android.R.id.content, fragment)
|
||||
.commitNow();
|
||||
final AtomicBoolean onBackPressedCalled = new AtomicBoolean(false);
|
||||
OnBackPressedCallback callback =
|
||||
new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
onBackPressedCalled.set(true);
|
||||
}
|
||||
};
|
||||
activity.getOnBackPressedDispatcher().addCallback(callback);
|
||||
|
||||
assertTrue(callback.isEnabled());
|
||||
FlutterActivityAndFragmentDelegate mockDelegate =
|
||||
mock(FlutterActivityAndFragmentDelegate.class);
|
||||
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
|
||||
fragment.setDelegateFactory(delegateFactory);
|
||||
|
||||
assertTrue(fragment.popSystemNavigator());
|
||||
assertTrue(callback.isEnabled());
|
||||
|
||||
verify(mockDelegate, never()).onBackPressed();
|
||||
assertTrue(onBackPressedCalled.get());
|
||||
assertTrue(callback.isEnabled());
|
||||
assertTrue(fragment.popSystemNavigator());
|
||||
|
||||
callback.setEnabled(false);
|
||||
assertFalse(callback.isEnabled());
|
||||
assertTrue(fragment.popSystemNavigator());
|
||||
verify(mockDelegate, never()).onBackPressed();
|
||||
assertTrue(onBackPressedCalled.get());
|
||||
assertTrue(callback.isEnabled());
|
||||
|
||||
verify(mockDelegate, never()).onBackPressed();
|
||||
assertFalse(callback.isEnabled());
|
||||
callback.setEnabled(false);
|
||||
assertFalse(callback.isEnabled());
|
||||
assertTrue(fragment.popSystemNavigator());
|
||||
|
||||
verify(mockDelegate, never()).onBackPressed();
|
||||
assertFalse(callback.isEnabled());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -42,6 +42,7 @@ import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.window.layout.FoldingFeature;
|
||||
@ -68,7 +69,6 @@ import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.Shadows;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
@ -248,16 +248,22 @@ public class FlutterViewTest {
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
@Test
|
||||
public void onConfigurationChanged_notifiesEngineOfDisplaySize() {
|
||||
FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class));
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
FlutterView flutterView = new FlutterView(activity);
|
||||
FlutterEngine flutterEngine =
|
||||
spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
|
||||
Configuration configuration = ctx.getResources().getConfiguration();
|
||||
Configuration configuration = ctx.getResources().getConfiguration();
|
||||
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
flutterView.onConfigurationChanged(configuration);
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
flutterView.onConfigurationChanged(configuration);
|
||||
|
||||
verify(flutterEngine, times(1))
|
||||
.updateDisplayMetrics(any(Float.class), any(Float.class), any(Float.class));
|
||||
verify(flutterEngine, times(1))
|
||||
.updateDisplayMetrics(any(Float.class), any(Float.class), any(Float.class));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void itSendsTextHidePasswordToFrameworkOnAttach() {
|
||||
@ -743,179 +749,193 @@ public class FlutterViewTest {
|
||||
@Config(minSdk = 28)
|
||||
public void onApplyWindowInsetsSetsDisplayCutouts() {
|
||||
// Use an Activity context so that FlutterView.onAttachedToWindow completes.
|
||||
Context context = Robolectric.setupActivity(Activity.class);
|
||||
FlutterView flutterView = spy(new FlutterView(context));
|
||||
assertEquals(0, flutterView.getSystemUiVisibility());
|
||||
when(flutterView.getWindowSystemUiVisibility()).thenReturn(0);
|
||||
when(flutterView.getContext()).thenReturn(context);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
FlutterView flutterView = spy(new FlutterView(activity));
|
||||
assertEquals(0, flutterView.getSystemUiVisibility());
|
||||
when(flutterView.getWindowSystemUiVisibility()).thenReturn(0);
|
||||
when(flutterView.getContext()).thenReturn(activity);
|
||||
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
|
||||
FlutterEngine flutterEngine =
|
||||
spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
|
||||
|
||||
// When we attach a new FlutterView to the engine without any system insets,
|
||||
// the viewport metrics default to 0.
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
|
||||
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop);
|
||||
// When we attach a new FlutterView to the engine without any system insets,
|
||||
// the viewport metrics default to 0.
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
|
||||
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop);
|
||||
|
||||
// Capture flutterView.setWindowInfoListenerDisplayFeatures.
|
||||
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
|
||||
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
|
||||
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
|
||||
ArgumentCaptor<Consumer<WindowLayoutInfo>> consumerCaptor =
|
||||
ArgumentCaptor.forClass(Consumer.class);
|
||||
flutterView.onAttachedToWindow();
|
||||
verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), consumerCaptor.capture());
|
||||
Consumer<WindowLayoutInfo> consumer = consumerCaptor.getValue();
|
||||
// Capture flutterView.setWindowInfoListenerDisplayFeatures.
|
||||
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
|
||||
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
|
||||
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
|
||||
ArgumentCaptor<Consumer<WindowLayoutInfo>> consumerCaptor =
|
||||
ArgumentCaptor.forClass(Consumer.class);
|
||||
flutterView.onAttachedToWindow();
|
||||
verify(windowInfoRepo)
|
||||
.addWindowLayoutInfoListener(any(), any(), consumerCaptor.capture());
|
||||
Consumer<WindowLayoutInfo> consumer = consumerCaptor.getValue();
|
||||
|
||||
// Set display features in flutterView to ensure they are not overridden by display cutouts.
|
||||
FoldingFeature displayFeature = mock(FoldingFeature.class);
|
||||
Rect featureBounds = new Rect(10, 20, 30, 40);
|
||||
when(displayFeature.getBounds()).thenReturn(featureBounds);
|
||||
when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL);
|
||||
when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT);
|
||||
WindowLayoutInfo windowLayout = new WindowLayoutInfo(Collections.singletonList(displayFeature));
|
||||
clearInvocations(flutterRenderer);
|
||||
consumer.accept(windowLayout);
|
||||
// Set display features in flutterView to ensure they are not overridden by display
|
||||
// cutouts.
|
||||
FoldingFeature displayFeature = mock(FoldingFeature.class);
|
||||
Rect featureBounds = new Rect(10, 20, 30, 40);
|
||||
when(displayFeature.getBounds()).thenReturn(featureBounds);
|
||||
when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL);
|
||||
when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT);
|
||||
WindowLayoutInfo windowLayout =
|
||||
new WindowLayoutInfo(Collections.singletonList(displayFeature));
|
||||
clearInvocations(flutterRenderer);
|
||||
consumer.accept(windowLayout);
|
||||
|
||||
// Assert the display feature is set.
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
List<FlutterRenderer.DisplayFeature> features =
|
||||
viewportMetricsCaptor.getValue().getDisplayFeatures();
|
||||
assertEquals(1, features.size());
|
||||
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state);
|
||||
assertEquals(featureBounds, features.get(0).bounds);
|
||||
// Assert the display feature is set.
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
List<FlutterRenderer.DisplayFeature> features =
|
||||
viewportMetricsCaptor.getValue().getDisplayFeatures();
|
||||
assertEquals(1, features.size());
|
||||
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state);
|
||||
assertEquals(featureBounds, features.get(0).bounds);
|
||||
|
||||
// Then we simulate the system applying a window inset.
|
||||
List<Rect> cutoutBoundingRects =
|
||||
Arrays.asList(new Rect(0, 200, 300, 400), new Rect(150, 0, 300, 150));
|
||||
WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects);
|
||||
// Then we simulate the system applying a window inset.
|
||||
List<Rect> cutoutBoundingRects =
|
||||
Arrays.asList(new Rect(0, 200, 300, 400), new Rect(150, 0, 300, 150));
|
||||
WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects);
|
||||
|
||||
clearInvocations(flutterRenderer);
|
||||
flutterView.onApplyWindowInsets(windowInsets);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
clearInvocations(flutterRenderer);
|
||||
flutterView.onApplyWindowInsets(windowInsets);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
|
||||
features = viewportMetricsCaptor.getValue().getDisplayFeatures();
|
||||
features = viewportMetricsCaptor.getValue().getDisplayFeatures();
|
||||
|
||||
// Assert the old display feature is still present.
|
||||
assertEquals(1, features.size());
|
||||
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state);
|
||||
assertEquals(featureBounds, features.get(0).bounds);
|
||||
// Assert the old display feature is still present.
|
||||
assertEquals(1, features.size());
|
||||
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state);
|
||||
assertEquals(featureBounds, features.get(0).bounds);
|
||||
|
||||
List<FlutterRenderer.DisplayFeature> cutouts =
|
||||
viewportMetricsCaptor.getValue().getDisplayCutouts();
|
||||
// Asserts for display cutouts.
|
||||
assertEquals(2, cutouts.size());
|
||||
for (int i = 0; i < 2; i++) {
|
||||
assertEquals(cutoutBoundingRects.get(i), cutouts.get(i).bounds);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutouts.get(i).type);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutouts.get(i).state);
|
||||
List<FlutterRenderer.DisplayFeature> cutouts =
|
||||
viewportMetricsCaptor.getValue().getDisplayCutouts();
|
||||
// Asserts for display cutouts.
|
||||
assertEquals(2, cutouts.size());
|
||||
for (int i = 0; i < 2; i++) {
|
||||
assertEquals(cutoutBoundingRects.get(i), cutouts.get(i).bounds);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutouts.get(i).type);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutouts.get(i).state);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// Robolectric.setupActivity
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
// getDefaultDisplay
|
||||
// TODO(jesswrd): https://github.com/flutter/flutter/issues/99421
|
||||
@Test
|
||||
public void itRegistersAndUnregistersToWindowManager() {
|
||||
Context context = Robolectric.setupActivity(Activity.class);
|
||||
FlutterView flutterView = spy(new FlutterView(context));
|
||||
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
|
||||
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
|
||||
// For reasoning behing using doReturn instead of when, read "Important gotcha" at
|
||||
// https://www.javadoc.io/doc/org.mockito/mockito-core/1.10.19/org/mockito/Mockito.html#13
|
||||
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
FlutterView flutterView = spy(new FlutterView(activity));
|
||||
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
|
||||
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
|
||||
// For reasoning behing using doReturn instead of when, read "Important gotcha" at
|
||||
// https://www.javadoc.io/doc/org.mockito/mockito-core/1.10.19/org/mockito/Mockito.html#13
|
||||
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
|
||||
|
||||
// When a new FlutterView is attached to the window
|
||||
flutterView.onAttachedToWindow();
|
||||
// When a new FlutterView is attached to the window
|
||||
flutterView.onAttachedToWindow();
|
||||
|
||||
// Then the WindowManager callback is registered
|
||||
verify(windowInfoRepo, times(1)).addWindowLayoutInfoListener(any(), any(), any());
|
||||
// Then the WindowManager callback is registered
|
||||
verify(windowInfoRepo, times(1)).addWindowLayoutInfoListener(any(), any(), any());
|
||||
|
||||
// When the FlutterView is detached from the window
|
||||
flutterView.onDetachedFromWindow();
|
||||
// When the FlutterView is detached from the window
|
||||
flutterView.onDetachedFromWindow();
|
||||
|
||||
// Then the WindowManager callback is unregistered
|
||||
verify(windowInfoRepo, times(1)).removeWindowLayoutInfoListener(any());
|
||||
// Then the WindowManager callback is unregistered
|
||||
verify(windowInfoRepo, times(1)).removeWindowLayoutInfoListener(any());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// Robolectric.setupActivity
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
// getDefaultDisplay
|
||||
// TODO(jesswrd): https://github.com/flutter/flutter/issues/99421
|
||||
@Test
|
||||
public void itSendsHingeDisplayFeatureToFlutter() {
|
||||
Context context = Robolectric.setupActivity(Activity.class);
|
||||
FlutterView flutterView = spy(new FlutterView(context));
|
||||
when(flutterView.getContext()).thenReturn(context);
|
||||
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
|
||||
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
|
||||
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
FlutterView flutterView = spy(new FlutterView(activity));
|
||||
when(flutterView.getContext()).thenReturn(activity);
|
||||
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
|
||||
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
|
||||
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
|
||||
FlutterEngine flutterEngine =
|
||||
spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
|
||||
|
||||
// Display features should be empty on attaching to engine.
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
|
||||
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
assertEquals(Collections.emptyList(), viewportMetricsCaptor.getValue().getDisplayFeatures());
|
||||
clearInvocations(flutterRenderer);
|
||||
// Display features should be empty on attaching to engine.
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
|
||||
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
assertEquals(
|
||||
Collections.emptyList(), viewportMetricsCaptor.getValue().getDisplayFeatures());
|
||||
clearInvocations(flutterRenderer);
|
||||
|
||||
// Test that display features do not override cutouts.
|
||||
List<Rect> cutoutBoundingRects = Collections.singletonList(new Rect(0, 200, 300, 400));
|
||||
WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects);
|
||||
flutterView.onApplyWindowInsets(windowInsets);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size());
|
||||
assertEquals(
|
||||
cutoutBoundingRects.get(0),
|
||||
viewportMetricsCaptor.getValue().getDisplayCutouts().get(0).bounds);
|
||||
clearInvocations(flutterRenderer);
|
||||
// Test that display features do not override cutouts.
|
||||
List<Rect> cutoutBoundingRects = Collections.singletonList(new Rect(0, 200, 300, 400));
|
||||
WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects);
|
||||
flutterView.onApplyWindowInsets(windowInsets);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size());
|
||||
assertEquals(
|
||||
cutoutBoundingRects.get(0),
|
||||
viewportMetricsCaptor.getValue().getDisplayCutouts().get(0).bounds);
|
||||
clearInvocations(flutterRenderer);
|
||||
|
||||
FoldingFeature displayFeature = mock(FoldingFeature.class);
|
||||
Rect featureRect = new Rect(0, 0, 100, 100);
|
||||
when(displayFeature.getBounds()).thenReturn(featureRect);
|
||||
when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL);
|
||||
when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT);
|
||||
FoldingFeature displayFeature = mock(FoldingFeature.class);
|
||||
Rect featureRect = new Rect(0, 0, 100, 100);
|
||||
when(displayFeature.getBounds()).thenReturn(featureRect);
|
||||
when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL);
|
||||
when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT);
|
||||
|
||||
WindowLayoutInfo testWindowLayout =
|
||||
new WindowLayoutInfo(Collections.singletonList(displayFeature));
|
||||
WindowLayoutInfo testWindowLayout =
|
||||
new WindowLayoutInfo(Collections.singletonList(displayFeature));
|
||||
|
||||
// When FlutterView is attached to the engine and window, and a hinge display feature exists
|
||||
flutterView.onAttachedToWindow();
|
||||
ArgumentCaptor<Consumer<WindowLayoutInfo>> wmConsumerCaptor =
|
||||
ArgumentCaptor.forClass(Consumer.class);
|
||||
verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), wmConsumerCaptor.capture());
|
||||
Consumer<WindowLayoutInfo> wmConsumer = wmConsumerCaptor.getValue();
|
||||
clearInvocations(flutterRenderer);
|
||||
wmConsumer.accept(testWindowLayout);
|
||||
// When FlutterView is attached to the engine and window, and a hinge display feature
|
||||
// exists
|
||||
flutterView.onAttachedToWindow();
|
||||
ArgumentCaptor<Consumer<WindowLayoutInfo>> wmConsumerCaptor =
|
||||
ArgumentCaptor.forClass(Consumer.class);
|
||||
verify(windowInfoRepo)
|
||||
.addWindowLayoutInfoListener(any(), any(), wmConsumerCaptor.capture());
|
||||
Consumer<WindowLayoutInfo> wmConsumer = wmConsumerCaptor.getValue();
|
||||
clearInvocations(flutterRenderer);
|
||||
wmConsumer.accept(testWindowLayout);
|
||||
|
||||
// Then the Renderer receives the display feature
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayFeatures().size());
|
||||
FlutterRenderer.DisplayFeature feature =
|
||||
viewportMetricsCaptor.getValue().getDisplayFeatures().get(0);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, feature.type);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, feature.state);
|
||||
assertEquals(featureRect, feature.bounds);
|
||||
// Then the Renderer receives the display feature
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayFeatures().size());
|
||||
FlutterRenderer.DisplayFeature feature =
|
||||
viewportMetricsCaptor.getValue().getDisplayFeatures().get(0);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, feature.type);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, feature.state);
|
||||
assertEquals(featureRect, feature.bounds);
|
||||
|
||||
// Assert the display cutout is unaffected.
|
||||
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size());
|
||||
FlutterRenderer.DisplayFeature cutout =
|
||||
viewportMetricsCaptor.getValue().getDisplayCutouts().get(0);
|
||||
assertEquals(cutoutBoundingRects.get(0), cutout.bounds);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutout.type);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutout.state);
|
||||
// Assert the display cutout is unaffected.
|
||||
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size());
|
||||
FlutterRenderer.DisplayFeature cutout =
|
||||
viewportMetricsCaptor.getValue().getDisplayCutouts().get(0);
|
||||
assertEquals(cutoutBoundingRects.get(0), cutout.bounds);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutout.type);
|
||||
assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutout.state);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@ import static org.mockito.Mockito.verify;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.view.PointerIcon;
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.embedding.android.FlutterView;
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
@ -21,7 +22,6 @@ import java.util.HashMap;
|
||||
import org.json.JSONException;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@Config(
|
||||
@ -31,34 +31,36 @@ import org.robolectric.annotation.Config;
|
||||
@TargetApi(API_LEVELS.API_24)
|
||||
public class MouseCursorPluginTest {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// Robolectric.setupActivity.
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
@Test
|
||||
public void mouseCursorPlugin_SetsSystemCursorOnRequest() throws JSONException {
|
||||
// Migrate to ActivityScenario by following https://github.com/robolectric/robolectric/pull/4736
|
||||
// Initialize a general MouseCursorPlugin.
|
||||
FlutterView testView = spy(new FlutterView(Robolectric.setupActivity(Activity.class)));
|
||||
MouseCursorChannel mouseCursorChannel = new MouseCursorChannel(mock(DartExecutor.class));
|
||||
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
|
||||
scenario.onActivity(
|
||||
activity -> {
|
||||
FlutterView testView = spy(new FlutterView(activity));
|
||||
MouseCursorChannel mouseCursorChannel =
|
||||
new MouseCursorChannel(mock(DartExecutor.class));
|
||||
|
||||
MouseCursorPlugin mouseCursorPlugin = new MouseCursorPlugin(testView, mouseCursorChannel);
|
||||
MouseCursorPlugin mouseCursorPlugin =
|
||||
new MouseCursorPlugin(testView, mouseCursorChannel);
|
||||
|
||||
final StoredResult methodResult = new StoredResult();
|
||||
mouseCursorChannel.synthesizeMethodCall(
|
||||
new MethodCall(
|
||||
"activateSystemCursor",
|
||||
new HashMap<String, Object>() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
final StoredResult methodResult = new StoredResult();
|
||||
mouseCursorChannel.synthesizeMethodCall(
|
||||
new MethodCall(
|
||||
"activateSystemCursor",
|
||||
new HashMap<String, Object>() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
{
|
||||
put("device", 1);
|
||||
put("kind", "text");
|
||||
}
|
||||
}),
|
||||
methodResult);
|
||||
verify(testView, times(1)).getSystemPointerIcon(PointerIcon.TYPE_TEXT);
|
||||
verify(testView, times(1)).setPointerIcon(any(PointerIcon.class));
|
||||
assertEquals(methodResult.result, Boolean.TRUE);
|
||||
{
|
||||
put("device", 1);
|
||||
put("kind", "text");
|
||||
}
|
||||
}),
|
||||
methodResult);
|
||||
verify(testView, times(1)).getSystemPointerIcon(PointerIcon.TYPE_TEXT);
|
||||
verify(testView, times(1)).setPointerIcon(any(PointerIcon.class));
|
||||
assertEquals(methodResult.result, Boolean.TRUE);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformChannel.Brightness;
|
||||
@ -645,51 +646,56 @@ public class PlatformPluginTest {
|
||||
verify(mockActivity, never()).finish();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// Robolectric.setupActivity.
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
@Test
|
||||
public void popSystemNavigatorFlutterFragment() {
|
||||
// Migrate to ActivityScenario by following https://github.com/robolectric/robolectric/pull/4736
|
||||
FragmentActivity activity = spy(Robolectric.setupActivity(FragmentActivity.class));
|
||||
final AtomicBoolean onBackPressedCalled = new AtomicBoolean(false);
|
||||
OnBackPressedCallback backCallback =
|
||||
new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
onBackPressedCalled.set(true);
|
||||
}
|
||||
};
|
||||
activity.getOnBackPressedDispatcher().addCallback(backCallback);
|
||||
try (ActivityScenario<FragmentActivity> scenario =
|
||||
ActivityScenario.launch(FragmentActivity.class)) {
|
||||
scenario.onActivity(
|
||||
fragmentActivity -> {
|
||||
FragmentActivity activity = spy(fragmentActivity);
|
||||
final AtomicBoolean onBackPressedCalled = new AtomicBoolean(false);
|
||||
OnBackPressedCallback backCallback =
|
||||
new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
onBackPressedCalled.set(true);
|
||||
}
|
||||
};
|
||||
activity.getOnBackPressedDispatcher().addCallback(backCallback);
|
||||
|
||||
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
|
||||
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(false);
|
||||
PlatformPlugin platformPlugin =
|
||||
new PlatformPlugin(activity, mockPlatformChannel, mockPlatformPluginDelegate);
|
||||
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
|
||||
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(false);
|
||||
PlatformPlugin platformPlugin =
|
||||
new PlatformPlugin(activity, mockPlatformChannel, mockPlatformPluginDelegate);
|
||||
|
||||
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
|
||||
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
|
||||
|
||||
verify(activity, never()).finish();
|
||||
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
|
||||
assertTrue(onBackPressedCalled.get());
|
||||
verify(activity, never()).finish();
|
||||
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
|
||||
assertTrue(onBackPressedCalled.get());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// Robolectric.setupActivity.
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
@Test
|
||||
public void doesNotDoAnythingByDefaultIfFragmentPopSystemNavigatorOverridden() {
|
||||
FragmentActivity activity = spy(Robolectric.setupActivity(FragmentActivity.class));
|
||||
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
|
||||
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(true);
|
||||
PlatformPlugin platformPlugin =
|
||||
new PlatformPlugin(activity, mockPlatformChannel, mockPlatformPluginDelegate);
|
||||
try (ActivityScenario<FragmentActivity> scenario =
|
||||
ActivityScenario.launch(FragmentActivity.class)) {
|
||||
scenario.onActivity(
|
||||
fragmentActivity -> {
|
||||
FragmentActivity activity = spy(fragmentActivity);
|
||||
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
|
||||
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(true);
|
||||
PlatformPlugin platformPlugin =
|
||||
new PlatformPlugin(activity, mockPlatformChannel, mockPlatformPluginDelegate);
|
||||
|
||||
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
|
||||
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
|
||||
|
||||
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
|
||||
// No longer perform the default action when overridden.
|
||||
verify(activity, never()).finish();
|
||||
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
|
||||
// No longer perform the default action when overridden.
|
||||
verify(activity, never()).finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -7,6 +7,10 @@
|
||||
<activity android:name="io.flutter.embedding.android.FlutterFragmentActivity" />
|
||||
|
||||
<activity android:name="io.flutter.embedding.android.FlutterFragmentActivityTest$FlutterFragmentActivityWithProvidedEngine" />
|
||||
|
||||
<activity android:name="androidx.fragment.app.FragmentActivity" />
|
||||
|
||||
<activity android:name="android.app.Activity" />
|
||||
|
||||
</application>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user