| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.browser.widget.bottomsheet; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertTrue; |
| |
| import static org.chromium.base.test.util.CriteriaHelper.pollUiThread; |
| import static org.chromium.chrome.browser.flags.ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE; |
| import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking; |
| |
| import androidx.test.filters.MediumTest; |
| |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import org.chromium.base.test.util.CallbackHelper; |
| import org.chromium.base.test.util.CommandLineFlags; |
| import org.chromium.base.test.util.Restriction; |
| import org.chromium.chrome.browser.toolbar.ToolbarManager; |
| import org.chromium.chrome.browser.ui.TabObscuringHandler; |
| import org.chromium.chrome.test.ChromeJUnit4ClassRunner; |
| import org.chromium.chrome.test.ChromeTabbedActivityTestRule; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent.HeightMode; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport; |
| import org.chromium.ui.test.util.UiRestriction; |
| |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.TimeoutException; |
| |
| /** This class tests the functionality of the {@link BottomSheet}. */ |
| @RunWith(ChromeJUnit4ClassRunner.class) |
| @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE) |
| @CommandLineFlags.Add({DISABLE_FIRST_RUN_EXPERIENCE}) |
| public class BottomSheetTest { |
| private static final float VELOCITY_WHEN_MOVING_UP = 1.0f; |
| private static final float VELOCITY_WHEN_MOVING_DOWN = -1.0f; |
| |
| @Rule |
| public ChromeTabbedActivityTestRule mTestRule = new ChromeTabbedActivityTestRule(); |
| private TestBottomSheetContent mLowPriorityContent; |
| private TestBottomSheetContent mHighPriorityContent; |
| private BottomSheetController mSheetController; |
| private BottomSheetTestSupport mTestSupport; |
| private int mSuppressToken; |
| |
| @Before |
| public void setUp() throws Exception { |
| BottomSheetTestSupport.setSmallScreen(false); |
| mTestRule.startMainActivityOnBlankPage(); |
| mSheetController = |
| mTestRule.getActivity().getRootUiCoordinatorForTesting().getBottomSheetController(); |
| mTestSupport = new BottomSheetTestSupport(mSheetController); |
| |
| runOnUiThreadBlocking(() -> { |
| mLowPriorityContent = new TestBottomSheetContent( |
| mTestRule.getActivity(), BottomSheetContent.ContentPriority.LOW, false); |
| mHighPriorityContent = new TestBottomSheetContent( |
| mTestRule.getActivity(), BottomSheetContent.ContentPriority.HIGH, false); |
| }); |
| mHighPriorityContent.setPeekHeight(HeightMode.DISABLED); |
| mHighPriorityContent.setHalfHeightRatio(0.5f); |
| mHighPriorityContent.setSkipHalfStateScrollingDown(false); |
| } |
| |
| @Test |
| @MediumTest |
| public void testCustomPeekRatio() { |
| int customToolbarHeight = TestBottomSheetContent.TOOLBAR_HEIGHT + 50; |
| mHighPriorityContent.setPeekHeight(customToolbarHeight); |
| |
| showContent(mHighPriorityContent, SheetState.PEEK); |
| |
| assertEquals("Sheet should be peeking at the custom height.", customToolbarHeight, |
| mSheetController.getCurrentOffset()); |
| } |
| |
| @Test |
| @MediumTest |
| public void testMovingDownFromFullClearsThresholdToReachHalfState() { |
| showContent(mHighPriorityContent, SheetState.FULL); |
| |
| assertEquals("Sheet should reach half state.", SheetState.HALF, |
| simulateScrollTo(0.6f * getMaxSheetHeightInPx(), VELOCITY_WHEN_MOVING_DOWN)); |
| } |
| |
| @Test |
| @MediumTest |
| public void testMovingDownFromFullDoesntClearThresholdToReachHalfState() { |
| showContent(mHighPriorityContent, SheetState.FULL); |
| |
| assertEquals("Sheet should remain in full state.", SheetState.FULL, |
| simulateScrollTo(0.9f * getMaxSheetHeightInPx(), VELOCITY_WHEN_MOVING_DOWN)); |
| } |
| |
| @Test |
| @MediumTest |
| public void testMovingUpFromHalfClearsThresholdToReachFullState() { |
| showContent(mHighPriorityContent, SheetState.HALF); |
| |
| assertEquals("Sheet should reach full state.", SheetState.FULL, |
| simulateScrollTo(0.8f * getMaxSheetHeightInPx(), VELOCITY_WHEN_MOVING_UP)); |
| } |
| |
| @Test |
| @MediumTest |
| public void testMovingUpFromHalfDoesntClearThresholdToReachHalfState() { |
| showContent(mHighPriorityContent, SheetState.HALF); |
| |
| assertEquals("Sheet should remain in half state.", SheetState.HALF, |
| simulateScrollTo(0.6f * getMaxSheetHeightInPx(), VELOCITY_WHEN_MOVING_UP)); |
| } |
| |
| @Test |
| @MediumTest |
| public void testMovingDownFromHalfClearsThresholdToReachHiddenState() { |
| showContent(mHighPriorityContent, SheetState.HALF); |
| |
| assertEquals("Sheet should reach hidden state.", SheetState.HIDDEN, |
| simulateScrollTo(0.2f * getMaxSheetHeightInPx(), VELOCITY_WHEN_MOVING_DOWN)); |
| } |
| |
| @Test |
| @MediumTest |
| public void testMovingDownFromHalfDoesntClearThresholdToReachHiddenState() { |
| showContent(mHighPriorityContent, SheetState.HALF); |
| |
| assertEquals("Sheet should remain in half state.", SheetState.HALF, |
| simulateScrollTo(0.4f * getMaxSheetHeightInPx(), VELOCITY_WHEN_MOVING_DOWN)); |
| } |
| |
| @Test |
| @MediumTest |
| public void testTabObscuringState() throws TimeoutException { |
| CallbackHelper obscuringStateChangedHelper = new CallbackHelper(); |
| TabObscuringHandler handler = mTestRule.getActivity().getTabObscuringHandler(); |
| handler.addObserver((isObscured) -> obscuringStateChangedHelper.notifyCalled()); |
| mHighPriorityContent.setHasCustomScrimLifecycle(false); |
| |
| assertFalse("The tab should not yet be obscured.", handler.areAllTabsObscured()); |
| |
| int callCount = obscuringStateChangedHelper.getCallCount(); |
| showContent(mHighPriorityContent, SheetState.HALF); |
| obscuringStateChangedHelper.waitForCallback("The tab should be obscured.", callCount); |
| assertTrue("The tab should be obscured.", handler.areAllTabsObscured()); |
| |
| callCount = obscuringStateChangedHelper.getCallCount(); |
| hideSheet(); |
| obscuringStateChangedHelper.waitForCallback("The tab should not be obscured.", callCount); |
| |
| assertFalse("The tab should not be obscured.", handler.areAllTabsObscured()); |
| } |
| |
| @Test |
| @MediumTest |
| public void testTabObscuringState_customScrim() throws ExecutionException { |
| CallbackHelper obscuringStateChangedHelper = new CallbackHelper(); |
| TabObscuringHandler handler = mTestRule.getActivity().getTabObscuringHandler(); |
| handler.addObserver((isObscured) -> obscuringStateChangedHelper.notifyCalled()); |
| mHighPriorityContent.setHasCustomScrimLifecycle(true); |
| |
| assertFalse("The tab should not be obscured.", handler.areAllTabsObscured()); |
| |
| showContent(mHighPriorityContent, SheetState.HALF); |
| assertFalse("The tab should still not be obscured.", handler.areAllTabsObscured()); |
| |
| hideSheet(); |
| |
| assertEquals("The obscuring handler should not have been called.", 0, |
| obscuringStateChangedHelper.getCallCount()); |
| } |
| |
| @Test |
| @MediumTest |
| public void testOmniboxFocusSuppressesSheet() { |
| ToolbarManager toolbarManager = |
| mTestRule.getActivity().getRootUiCoordinatorForTesting().getToolbarManager(); |
| showContent(mHighPriorityContent, SheetState.HALF); |
| |
| runOnUiThreadBlocking(() -> toolbarManager.setUrlBarFocus(true, 0)); |
| |
| assertEquals("The bottom sheet should be hidden.", SheetState.HIDDEN, |
| mSheetController.getSheetState()); |
| |
| runOnUiThreadBlocking(() -> toolbarManager.setUrlBarFocus(false, 0)); |
| |
| assertNotEquals("The bottom sheet should not be hidden.", SheetState.HIDDEN, |
| mSheetController.getSheetState()); |
| } |
| |
| @Test |
| @MediumTest |
| public void testSuppressionState_unsuppress() { |
| showContent(mHighPriorityContent, SheetState.HALF); |
| |
| runOnUiThreadBlocking(() -> { |
| mSuppressToken = mTestSupport.suppressSheet(StateChangeReason.NONE); |
| mTestSupport.endAllAnimations(); |
| }); |
| |
| assertEquals("The sheet should be in the hidden state.", SheetState.HIDDEN, |
| mSheetController.getSheetState()); |
| |
| runOnUiThreadBlocking(() -> { |
| mTestSupport.unsuppressSheet(mSuppressToken); |
| mTestSupport.endAllAnimations(); |
| }); |
| |
| assertEquals("The sheet should be restored to the half state.", SheetState.HALF, |
| mSheetController.getSheetState()); |
| } |
| |
| @Test |
| @MediumTest |
| public void testSuppressionState_unsuppressDifferentContent() { |
| showContent(mHighPriorityContent, SheetState.HALF); |
| |
| runOnUiThreadBlocking(() -> { |
| mSuppressToken = mTestSupport.suppressSheet(StateChangeReason.NONE); |
| mTestSupport.endAllAnimations(); |
| }); |
| |
| assertEquals("The sheet should be in the hidden state.", SheetState.HIDDEN, |
| mSheetController.getSheetState()); |
| |
| runOnUiThreadBlocking(() -> mSheetController.hideContent(mHighPriorityContent, false)); |
| |
| showContent(mLowPriorityContent, SheetState.PEEK); |
| |
| runOnUiThreadBlocking(() -> { |
| mTestSupport.unsuppressSheet(mSuppressToken); |
| mTestSupport.endAllAnimations(); |
| }); |
| |
| assertEquals("The sheet should be restored to the peek state.", SheetState.PEEK, |
| mSheetController.getSheetState()); |
| } |
| |
| private void hideSheet() { |
| runOnUiThreadBlocking(() -> mTestSupport.setSheetState(SheetState.HIDDEN, false)); |
| } |
| |
| private float getMaxSheetHeightInPx() { |
| return mSheetController.getContainerHeight(); |
| } |
| |
| private @SheetState int simulateScrollTo(float targetHeightInPx, float yUpwardsVelocity) { |
| return mTestSupport.forceScrolling(targetHeightInPx, yUpwardsVelocity); |
| } |
| |
| /** @param content The content to show in the bottom sheet. */ |
| private void showContent(BottomSheetContent content, @SheetState int targetState) { |
| runOnUiThreadBlocking(() -> { |
| boolean shown = mSheetController.requestShowContent(content, false); |
| if (shown) { |
| mTestSupport.setSheetState(targetState, false); |
| } else { |
| assertEquals("The sheet should still be hidden.", SheetState.HIDDEN, |
| mSheetController.getSheetState()); |
| } |
| }); |
| |
| // If the content switched, wait for the desired state. |
| if (mSheetController.getCurrentSheetContent() == content) { |
| pollUiThread(() -> mSheetController.getSheetState() == targetState); |
| } |
| } |
| } |