blob: 33ff028c7822563f266581ceb92168a0c0cbf69f [file] [log] [blame]
// Copyright 2017 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.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.support.test.filters.MediumTest;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.util.MathUtils;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.HeightMode;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.SheetState;
import org.chromium.chrome.test.BottomSheetTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.ui.test.util.UiRestriction;
import java.util.concurrent.TimeoutException;
/** This class tests the functionality of the {@link BottomSheetObserver}. */
@RunWith(ChromeJUnit4ClassRunner.class)
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE) // Bottom sheet is only used on phones.
public class BottomSheetObserverTest {
@Rule
public BottomSheetTestRule mBottomSheetTestRule = new BottomSheetTestRule();
private BottomSheetTestRule.Observer mObserver;
private TestBottomSheetContent mSheetContent;
@Before
public void setUp() throws Exception {
mBottomSheetTestRule.startMainActivityOnBottomSheet(SheetState.HIDDEN);
ThreadUtils.runOnUiThreadBlocking(() -> {
mSheetContent = new TestBottomSheetContent(
mBottomSheetTestRule.getActivity(), BottomSheet.ContentPriority.HIGH, false);
mBottomSheetTestRule.getBottomSheet().showContent(mSheetContent);
});
mObserver = mBottomSheetTestRule.getObserver();
}
/** Test that the onSheetClosed event is triggered if the sheet is closed without animation. */
@Test
@MediumTest
public void testCloseEventCalled_noAnimation() throws TimeoutException {
runCloseEventTest(false, true);
}
/**
* Test that the onSheetClosed event is triggered if the sheet is closed without animation and
* without a peeking state.
*/
@Test
@MediumTest
public void testCloseEventCalled_noAnimationNoPeekState() throws TimeoutException {
runCloseEventTest(false, false);
}
/** Test that the onSheetClosed event is triggered if the sheet is closed with animation. */
@Test
@MediumTest
public void testCloseEventCalled_withAnimation() throws TimeoutException {
runCloseEventTest(true, true);
}
/**
* Test that the onSheetClosed event is triggered if the sheet is closed with animation but
* without a peeking state.
*/
@Test
@MediumTest
public void testCloseEventCalled_withAnimationNoPeekState() throws TimeoutException {
runCloseEventTest(true, false);
}
/**
* Run different versions of the onSheetClosed event test.
* @param animationEnabled Whether to run the test with animation.
* @param peekStateEnabled Whether the sheet's content has a peek state.
*/
private void runCloseEventTest(boolean animationEnabled, boolean peekStateEnabled)
throws TimeoutException {
mBottomSheetTestRule.setSheetState(SheetState.FULL, false);
mSheetContent.setPeekHeight(peekStateEnabled ? HeightMode.DEFAULT : HeightMode.DISABLED);
CallbackHelper closedCallbackHelper = mObserver.mClosedCallbackHelper;
int initialOpenedCount = mObserver.mOpenedCallbackHelper.getCallCount();
int closedCallbackCount = closedCallbackHelper.getCallCount();
mBottomSheetTestRule.setSheetState(
peekStateEnabled ? SheetState.PEEK : SheetState.HIDDEN, animationEnabled);
closedCallbackHelper.waitForCallback(closedCallbackCount, 1);
assertEquals(initialOpenedCount, mObserver.mOpenedCallbackHelper.getCallCount());
}
/** Test that the onSheetOpened event is triggered if the sheet is opened without animation. */
@Test
@MediumTest
public void testOpenedEventCalled_noAnimation() throws TimeoutException {
runOpenEventTest(false, true);
}
/**
* Test that the onSheetOpened event is triggered if the sheet is opened without animation and
* without a peeking state.
*/
@Test
@MediumTest
public void testOpenedEventCalled_noAnimationNoPeekState() throws TimeoutException {
runOpenEventTest(false, false);
}
/** Test that the onSheetOpened event is triggered if the sheet is opened with animation. */
@Test
@MediumTest
public void testOpenedEventCalled_withAnimation() throws TimeoutException {
runOpenEventTest(true, true);
}
/**
* Test that the onSheetOpened event is triggered if the sheet is opened with animation and
* without a peek state.
*/
@Test
@MediumTest
public void testOpenedEventCalled_withAnimationNoPeekState() throws TimeoutException {
runOpenEventTest(true, false);
}
/**
* Run different versions of the onSheetOpened event test.
* @param animationEnabled Whether to run the test with animation.
* @param peekStateEnabled Whether the sheet's content has a peek state.
*/
private void runOpenEventTest(boolean animationEnabled, boolean peekStateEnabled)
throws TimeoutException {
mSheetContent.setPeekHeight(peekStateEnabled ? HeightMode.DEFAULT : HeightMode.DISABLED);
CallbackHelper openedCallbackHelper = mObserver.mOpenedCallbackHelper;
int openedCallbackCount = openedCallbackHelper.getCallCount();
CallbackHelper closedCallbackHelper = mObserver.mClosedCallbackHelper;
int initialClosedCount = closedCallbackHelper.getCallCount();
mBottomSheetTestRule.setSheetState(
mBottomSheetTestRule.getBottomSheet().getOpeningState(), false);
assertNotEquals("Sheet should not be hidden.",
mBottomSheetTestRule.getBottomSheet().getSheetState(), SheetState.HIDDEN);
if (!peekStateEnabled) {
assertNotEquals("Sheet should be above the peeking state when peek is disabled.",
mBottomSheetTestRule.getBottomSheet().getSheetState(), SheetState.PEEK);
}
mBottomSheetTestRule.setSheetState(SheetState.FULL, animationEnabled);
openedCallbackHelper.waitForCallback(openedCallbackCount, 1);
assertEquals(initialClosedCount, closedCallbackHelper.getCallCount());
}
/**
* Test the onOffsetChanged event.
*/
@Test
@MediumTest
public void testOffsetChangedEvent() throws TimeoutException {
mBottomSheetTestRule.setSheetState(SheetState.FULL, false);
CallbackHelper callbackHelper = mObserver.mOffsetChangedCallbackHelper;
BottomSheet bottomSheet = mBottomSheetTestRule.getBottomSheet();
float hiddenHeight = bottomSheet.getHiddenRatio() * bottomSheet.getSheetContainerHeight();
float fullHeight = bottomSheet.getFullRatio() * bottomSheet.getSheetContainerHeight();
// The sheet's half state is not necessarily 50% of the way to the top.
float midPeekFull = (hiddenHeight + fullHeight) / 2f;
// When in the hidden state, the transition value should be 0.
int callbackCount = callbackHelper.getCallCount();
mBottomSheetTestRule.setSheetOffsetFromBottom(hiddenHeight);
callbackHelper.waitForCallback(callbackCount, 1);
assertEquals(0f, mObserver.getLastOffsetChangedValue(), MathUtils.EPSILON);
// When in the full state, the transition value should be 1.
callbackCount = callbackHelper.getCallCount();
mBottomSheetTestRule.setSheetOffsetFromBottom(fullHeight);
callbackHelper.waitForCallback(callbackCount, 1);
assertEquals(1f, mObserver.getLastOffsetChangedValue(), MathUtils.EPSILON);
// Halfway between peek and full should send 0.5.
callbackCount = callbackHelper.getCallCount();
mBottomSheetTestRule.setSheetOffsetFromBottom(midPeekFull);
callbackHelper.waitForCallback(callbackCount, 1);
assertEquals(0.5f, mObserver.getLastOffsetChangedValue(), MathUtils.EPSILON);
}
@Test
@MediumTest
public void testWrapContentBehavior() throws TimeoutException {
// We make sure the height of the wrapped content is smaller than sheetContainerHeight.
BottomSheet bottomSheet = mBottomSheetTestRule.getBottomSheet();
int wrappedContentHeight = (int) bottomSheet.getSheetContainerHeight() / 2;
assertTrue(wrappedContentHeight > 0);
// Show content that should be wrapped.
CallbackHelper callbackHelper = mObserver.mContentChangedCallbackHelper;
int callCount = callbackHelper.getCallCount();
ThreadUtils.runOnUiThreadBlocking(() -> {
bottomSheet.showContent(new TestBottomSheetContent(
mBottomSheetTestRule.getActivity(), BottomSheet.ContentPriority.HIGH, false) {
private final ViewGroup mContentView;
{
// We wrap the View in a FrameLayout as we need something to read the hard coded
// height in the layout params. There is no way to create a View with a specific
// height on its own as View::onMeasure will by default set its height/width to
// be the minimum height/width of its background (if any) or expand as much as
// it can.
mContentView = new FrameLayout(mBottomSheetTestRule.getActivity());
View child = new View(mBottomSheetTestRule.getActivity());
child.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, wrappedContentHeight));
mContentView.addView(child);
}
@Override
public View getContentView() {
return mContentView;
}
@Override
public boolean wrapContentEnabled() {
return true;
}
});
});
callbackHelper.waitForCallback(callCount);
// HALF state is forbidden when wrapping the content.
mBottomSheetTestRule.setSheetState(SheetState.HALF, false);
assertEquals(SheetState.FULL, bottomSheet.getSheetState());
// Check the offset.
assertEquals(wrappedContentHeight + bottomSheet.getToolbarShadowHeight(),
bottomSheet.getCurrentOffsetPx(), MathUtils.EPSILON);
}
}