// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui.widget;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.PopupWindow;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowView;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.ui.R;
import org.chromium.ui.base.LocalizationUtils;
import org.chromium.ui.widget.AnchoredPopupWindow.HorizontalOrientation;
import org.chromium.ui.widget.AnchoredPopupWindow.PopupSpec;
import org.chromium.ui.widget.AnchoredPopupWindow.VerticalOrientation;
/** Unit tests for the static positioning methods in {@link AnchoredPopupWindow}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE, shadows = ShadowView.class, qualifiers = "w600dp-h1000dp-mdpi")
public final class AnchoredPopupWindowTest {
private Rect mWindowRect;
int mRootWidth;
int mRootHeight;
int mPopupWidth;
int mPopupHeight;
// Default settings for AnchoredPopup.
private int mPaddingX;
private int mPaddingY;
private int mMarginPx;
private int mMaxWidthPx;
private int mDesiredWidthPx;
private @HorizontalOrientation int mPreferredHorizontalOrientation;
private @VerticalOrientation int mPreferredVerticalOrientation;
private boolean mCurrentPositionBelow;
private boolean mCurrentPositionToLeft;
private boolean mPreferCurrentOrientation;
private boolean mHorizontalOverlapAnchor;
private boolean mVerticalOverlapAnchor;
private boolean mSmartAnchorWithMaxWidth;
private FrameLayout mContentView;
private Activity mActivity;
@Before
public void setUp() {
mRootWidth = 600;
mRootHeight = 1000;
mPopupWidth = 150;
mPopupHeight = 300;
mWindowRect = new Rect(0, 0, mRootWidth, mRootHeight);
mActivity = Robolectric.buildActivity(Activity.class).get();
mContentView = new FrameLayout(mActivity);
mContentView.setMinimumWidth(mPopupWidth);
mContentView.setMinimumHeight(mPopupHeight);
setDefaultValueForAnchoredPopup();
}
@After
public void tearDown() {
mActivity.finish();
UiWidgetFactory.setInstance(null);
}
@Test
public void testGetPopupPosition_BelowRight() {
Rect anchorRect = new Rect(10, 10, 20, 20);
int spaceLeftOfAnchor =
AnchoredPopupWindow.getSpaceLeftOfAnchor(anchorRect, mWindowRect, false);
int spaceRightOfAnchor =
AnchoredPopupWindow.getSpaceRightOfAnchor(anchorRect, mWindowRect, false);
boolean positionToLeft =
AnchoredPopupWindow.shouldPositionLeftOfAnchor(
spaceLeftOfAnchor, spaceRightOfAnchor, mPopupWidth, false, false);
assertEquals("Space left of anchor incorrect.", 10, spaceLeftOfAnchor);
assertEquals("Space right of anchor incorrect.", 580, spaceRightOfAnchor);
assertFalse("positionToLeft incorrect.", positionToLeft);
int x =
AnchoredPopupWindow.getPopupX(
anchorRect,
mWindowRect,
mPopupWidth,
0,
false,
AnchoredPopupWindow.HorizontalOrientation.MAX_AVAILABLE_SPACE,
false);
int y = AnchoredPopupWindow.getPopupY(anchorRect, mPopupHeight, false, true);
assertEquals("Wrong x position.", 20, x);
assertEquals("Wrong y position.", 20, y);
}
@Test
public void testGetPopupPosition_BelowRight_Overlap() {
Rect anchorRect = new Rect(10, 10, 20, 20);
int spaceLeftOfAnchor =
AnchoredPopupWindow.getSpaceLeftOfAnchor(anchorRect, mWindowRect, true);
int spaceRightOfAnchor =
AnchoredPopupWindow.getSpaceRightOfAnchor(anchorRect, mWindowRect, true);
boolean positionToLeft =
AnchoredPopupWindow.shouldPositionLeftOfAnchor(
spaceLeftOfAnchor, spaceRightOfAnchor, mPopupWidth, false, false);
assertEquals("Space left of anchor incorrect.", 20, spaceLeftOfAnchor);
assertEquals("Space right of anchor incorrect.", 590, spaceRightOfAnchor);
assertFalse("positionToLeft incorrect.", positionToLeft);
int x =
AnchoredPopupWindow.getPopupX(
anchorRect,
mWindowRect,
mPopupWidth,
0,
true,
AnchoredPopupWindow.HorizontalOrientation.MAX_AVAILABLE_SPACE,
false);
int y = AnchoredPopupWindow.getPopupY(anchorRect, mPopupHeight, true, true);
assertEquals("Wrong x position.", 10, x);
assertEquals("Wrong y position.", 10, y);
}
@Test
public void testGetPopupPosition_BelowCenter() {
Rect anchorRect = new Rect(295, 10, 305, 20);
int x =
AnchoredPopupWindow.getPopupX(
anchorRect,
mWindowRect,
mPopupWidth,
0,
false,
AnchoredPopupWindow.HorizontalOrientation.CENTER,
false);
int y = AnchoredPopupWindow.getPopupY(anchorRect, mPopupHeight, false, true);
assertEquals("Wrong x position.", 225, x);
assertEquals("Wrong y position.", 20, y);
}
@Test
public void getPopupPosition_AboveLeft() {
Rect anchorRect = new Rect(400, 800, 410, 820);
int spaceLeftOfAnchor =
AnchoredPopupWindow.getSpaceLeftOfAnchor(anchorRect, mWindowRect, false);
int spaceRightOfAnchor =
AnchoredPopupWindow.getSpaceRightOfAnchor(anchorRect, mWindowRect, false);
boolean positionToLeft =
AnchoredPopupWindow.shouldPositionLeftOfAnchor(
spaceLeftOfAnchor, spaceRightOfAnchor, mPopupWidth, false, false);
assertEquals("Space left of anchor incorrect.", 400, spaceLeftOfAnchor);
assertEquals("Space right of anchor incorrect.", 190, spaceRightOfAnchor);
assertTrue("positionToLeft incorrect.", positionToLeft);
int x =
AnchoredPopupWindow.getPopupX(
anchorRect,
mWindowRect,
mPopupWidth,
0,
false,
AnchoredPopupWindow.HorizontalOrientation.MAX_AVAILABLE_SPACE,
positionToLeft);
int y = AnchoredPopupWindow.getPopupY(anchorRect, mPopupHeight, false, false);
assertEquals("Wrong x position.", 250, x);
assertEquals("Wrong y position.", 500, y);
}
@Test
public void testGetPopupPosition_AboveLeft_Overlap() {
Rect anchorRect = new Rect(400, 800, 410, 820);
int spaceLeftOfAnchor =
AnchoredPopupWindow.getSpaceLeftOfAnchor(anchorRect, mWindowRect, true);
int spaceRightOfAnchor =
AnchoredPopupWindow.getSpaceRightOfAnchor(anchorRect, mWindowRect, true);
boolean positionToLeft =
AnchoredPopupWindow.shouldPositionLeftOfAnchor(
spaceLeftOfAnchor, spaceRightOfAnchor, mPopupWidth, false, false);
assertEquals("Space left of anchor incorrect.", 410, spaceLeftOfAnchor);
assertEquals("Space right of anchor incorrect.", 200, spaceRightOfAnchor);
assertTrue("positionToLeft incorrect.", positionToLeft);
int x =
AnchoredPopupWindow.getPopupX(
anchorRect,
mWindowRect,
mPopupWidth,
0,
true,
AnchoredPopupWindow.HorizontalOrientation.MAX_AVAILABLE_SPACE,
true);
int y = AnchoredPopupWindow.getPopupY(anchorRect, mPopupHeight, true, false);
assertEquals("Wrong x position.", 260, x);
assertEquals("Wrong y position.", 520, y);
}
@Test
public void testGetPopupPosition_ClampedLeftEdge() {
Rect anchorRect = new Rect(10, 10, 20, 20);
int x =
AnchoredPopupWindow.getPopupX(
anchorRect,
mWindowRect,
mPopupWidth,
20,
false,
AnchoredPopupWindow.HorizontalOrientation.MAX_AVAILABLE_SPACE,
true);
assertEquals("Wrong x position.", 20, x);
}
@Test
public void testGetPopupPosition_ClampedRightEdge() {
Rect anchorRect = new Rect(590, 800, 600, 820);
int x =
AnchoredPopupWindow.getPopupX(
anchorRect,
mWindowRect,
mPopupWidth,
20,
false,
AnchoredPopupWindow.HorizontalOrientation.MAX_AVAILABLE_SPACE,
true);
assertEquals("Wrong x position.", 430, x);
}
@Test
public void testShouldPositionLeftOfAnchor() {
Rect anchorRect = new Rect(300, 10, 310, 20);
int spaceLeftOfAnchor =
AnchoredPopupWindow.getSpaceLeftOfAnchor(anchorRect, mWindowRect, false);
int spaceRightOfAnchor =
AnchoredPopupWindow.getSpaceRightOfAnchor(anchorRect, mWindowRect, false);
boolean positionToLeft =
AnchoredPopupWindow.shouldPositionLeftOfAnchor(
spaceLeftOfAnchor, spaceRightOfAnchor, mPopupWidth, false, false);
assertEquals("Space left of anchor incorrect.", 300, spaceLeftOfAnchor);
assertEquals("Space right of anchor incorrect.", 290, spaceRightOfAnchor);
assertTrue("Should be positioned to the left.", positionToLeft);
anchorRect = new Rect(250, 10, 260, 20);
spaceLeftOfAnchor =
AnchoredPopupWindow.getSpaceLeftOfAnchor(anchorRect, mWindowRect, false);
spaceRightOfAnchor =
AnchoredPopupWindow.getSpaceRightOfAnchor(anchorRect, mWindowRect, false);
positionToLeft =
AnchoredPopupWindow.shouldPositionLeftOfAnchor(
spaceLeftOfAnchor, spaceRightOfAnchor, mPopupWidth, true, true);
// There is more space to the right, but the popup will still fit to the left and should
// be positioned to the left.
assertEquals("Space left of anchor incorrect.", 250, spaceLeftOfAnchor);
assertEquals("Space right of anchor incorrect.", 340, spaceRightOfAnchor);
assertTrue("Should still be positioned to the left.", positionToLeft);
}
@Test
public void testGetMaxContentWidth() {
int maxWidth = AnchoredPopupWindow.getMaxContentWidth(300, 600, 10, 10);
assertEquals("Max width should be based on desired width.", 290, maxWidth);
maxWidth = AnchoredPopupWindow.getMaxContentWidth(300, 300, 10, 10);
assertEquals("Max width should be based on root view width.", 270, maxWidth);
maxWidth = AnchoredPopupWindow.getMaxContentWidth(0, 600, 10, 10);
assertEquals(
"Max width should be based on root view width when desired with is 0.",
570,
maxWidth);
maxWidth = AnchoredPopupWindow.getMaxContentWidth(300, 300, 10, 300);
assertEquals("Max width should be clamped at 0.", 0, maxWidth);
}
// Test cases using #doTestAnchoredPopupAtRect.
// All the test cases are explained with the abbreviation:
// anchorRect => A, expectedPopupRect => E, width = w, height = h
@Test
public void testCalcPopupRect_DefaultSettings() {
// E.left = A.right = 0
// E.top = A.bottom = 0
// E.right = A.right + w = 150
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Anchored on bottom right.",
/*anchorRect*/ new Rect(0, 0, 0, 0),
/*expectedPopupRect*/ new Rect(0, 0, 150, 300));
// E.left = A.left-w = 500 - 150 = 350
// E.top = A.bottom = 0
// E.right = A.left = 500
// E.bottom = A.bottom + h = 0 + 300
doTestAnchoredPopupAtRect(
"Anchored on bottom left.",
/*anchorRect*/ new Rect(500, 0, 500, 0),
/*expectedPopupRect*/ new Rect(350, 0, 500, 300));
// E.left = A.right = 0
// E.top = A.top - h = 800 - 300 = 500
// E.right = A.right + w = 0 + 150
doTestAnchoredPopupAtRect(
"Anchored on top right.",
/*anchorRect*/ new Rect(0, 800, 0, 800),
/*expectedPopupRect*/ new Rect(0, 500, 150, 800));
// E.left = A.left - w = 600 - 150 = 450
// E.top = A.top - h = 1000 - 300 = 700
// E.right = A.left = 600
// E.bottom = A.top = 1000
doTestAnchoredPopupAtRect(
"Anchored on top left due to space limit.",
/*anchorRect*/ new Rect(600, 1000, 600, 1000),
/*expectedPopupRect*/ new Rect(450, 700, 600, 1000));
}
@Test
public void testCalcPopupRect_BiasOnX() {
// E.left = A.left - w = 300 - 150 = 150
// E.top = A.bottom = 0
// E.right = A.left = 300
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Bias left when space is equal.",
/*anchorRect*/ new Rect(300, 0, 300, 0),
/*expectedPopupRect*/ new Rect(150, 0, 300, 300));
// E.left = A.left - w = 200 - 150 = 50
// E.top = A.top = 0
// E.right = A.left = 200
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Bias left when it has more space.",
/*anchorRect*/ new Rect(200, 0, 450, 0),
/*expectedPopupRect*/ new Rect(50, 0, 200, 300));
// E.left = A.right = 300
// E.top = A.bottom = 0
// E.right = A.right + w = 450
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Bias right when it has more space.",
/*anchorRect*/ new Rect(150, 0, 300, 0),
/*expectedPopupRect*/ new Rect(300, 0, 450, 300));
}
@Test
public void testCalcPopupRect_BiasOnY() {
// E.left = A.right = 0
// E.top = A.bottom = 500
// E.right = A.right + w = 150
// E.bottom = A.bottom + h = 500 + 300 = 800
doTestAnchoredPopupAtRect(
"Bias below when space is equal.",
/*anchorRect*/ new Rect(0, 500, 0, 500),
/*expectedPopupRect*/ new Rect(0, 500, 150, 800));
// E.left = A.right = 0
// E.top = A.top - h = 600 - 300 = 300
// E.right = A.right + w = 150
// E.bottom = A.top = 600
doTestAnchoredPopupAtRect(
"Bias top when it has more space.",
/*anchorRect*/ new Rect(0, 600, 0, 600),
/*expectedPopupRect*/ new Rect(0, 300, 150, 600));
// E.left = A.right = 0
// E.top = A.bottom = 300
// E.right = A.right + w = 150
// E.bottom = A.bottom + h = 300 + 300 = 600
doTestAnchoredPopupAtRect(
"Bias below when it has more space",
/*anchorRect*/ new Rect(0, 300, 0, 300),
/*expectedPopupRect*/ new Rect(0, 300, 150, 600));
}
@Test
public void testCalcPopupRect_LimitedSpaceX() {
// When both left / right side does not have enough space, the anchor will have to overlap
// with the anchor.
// E.left = max(window.left, A.left - w) = max(0, 100 - 150) = 0
// E.top = A.bottom = 0
// E.right = E.left + w = 0 + 150 = 150
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Force overlap with anchor rect on left.",
/*anchorRect*/ new Rect(100, 0, 600, 0),
/*expectedPopupRect*/ new Rect(0, 0, 150, 300));
// E.right = min(window.right, A.right + w) = min(600, 500 + 150) = 600
// E.left = E.right - w = 600 - 150 = 450
// E.top = A.bottom = 0
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Force overlap with anchor rect on right.",
/*anchorRect*/ new Rect(0, 0, 500, 0),
/*expectedPopupRect*/ new Rect(450, 0, 600, 300));
}
@Test
public void testCalcPopupRect_LimitedSpaceY() {
// Since mVerticalOverlapAnchor = false, even when space allows on the side, the popup
// will not show on the side of anchor rect.
// E.left = A.right = 0
// E.top = A.bottom = 950
// E.right = A.right + w = 150
// E.bottom = min(window.bottom, A.bottom + h) = min(1000, 950+300) = 1000
doTestAnchoredPopupAtRect(
"Both above and below does not have enough space, anchored below due to bias. "
+ "Reduce the height to fit into left over space on bottom.",
/*anchorRect*/ new Rect(0, 100, 0, 950),
/*expectedPopupRect*/ new Rect(0, 950, 150, 1000));
}
@Test
public void testCalcPopupRect_Margin() {
// TODO(crbug.com/40831293): Margin needs to be considered on Y axis.
mMarginPx = 10;
// E.left = A.right + margin = 0 + 10 = 10
// E.top = A.bottom = 0
// E.right = A.right + w + margin = 150 + 10 = 160
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Leave margin between screen and anchor rect.",
/*anchorRect*/ new Rect(0, 0, 0, 0),
/*expectedPopupRect*/ new Rect(10, 0, 160, 300));
}
@Test
public void testCalcPopupRect_Padding() {
mPaddingX = 3;
mPaddingY = 2;
// E.left = A.right = 0
// E.top = A.bottom = 0
// E.right = A.right + w + paddingX = 0 + 150 + 3 = 153
// E.bottom = A.bottom + h + paddingY = 0 + 300 + 2 = 302
doTestAnchoredPopupAtRect(
"Adding padding into popup rect size.",
/*anchorRect*/ new Rect(0, 0, 0, 0),
/*expectedPopupRect*/ new Rect(0, 0, 153, 302));
}
@Test
public void testCalcPopupRect_MaxWidth() {
mMaxWidthPx = 200;
// E.left = A.right = 0
// E.top = A.bottom = 0
// E.right = A.right + min(w, maxWidth) = 0 + min(150, 200) = 150
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Max width greater than expected size.",
/*anchorRect*/ new Rect(0, 0, 0, 0),
/*expectedPopupRect*/ new Rect(0, 0, 150, 300));
mMaxWidthPx = 100;
// E.left = A.right = 0
// E.top = A.bottom = 0
// E.right = A.right + min(w, maxWidth) = 0 + min(150, 100) = 100
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Max width limited to 100.",
/*anchorRect*/ new Rect(0, 0, 0, 0),
/*expectedPopupRect*/ new Rect(0, 0, 100, 300));
}
@Test
public void testCalcPopupRect_DesiredWidth() {
mDesiredWidthPx = 200;
// E.left = A.right = 0
// E.top = A.bottom = 0
// E.right = A.right + desiredWidth = 0 + 200 = 200
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Popup shown as desired width.",
/*anchorRect*/ new Rect(0, 0, 0, 0),
/*expectedPopupRect*/ new Rect(0, 0, 200, 300));
// E.left = max(window.left, A.left - desiredWidth) = max(0, 150-200) = 0
// E.top = A.bottom = 0
// E.right = E.left + desiredWidth = 200
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Shown as desired width even when available space is less.",
/*anchorRect*/ new Rect(150, 0, 600, 0),
/*expectedPopupRect*/ new Rect(0, 0, 200, 300));
// E.left = A.right = 0
// E.top = A.bottom = 0
// E.right = A.right + min(maxWidth, desiredWidth) = 0 + min(180, 200) = 180
// E.bottom = A.bottom + h = 300
mMaxWidthPx = 180;
doTestAnchoredPopupAtRect(
"Desired width will respect a smaller max width.",
/*anchorRect*/ new Rect(0, 0, 0, 0),
/*expectedPopupRect*/ new Rect(0, 0, 180, 300));
// E.left = A.right = 0
// E.top = A.bottom = 0
// E.right = A.right + min(maxWidth, desiredWidth) = 0 + min(300, 200) = 200
// E.bottom = A.bottom + h = 300
mMaxWidthPx = 300;
doTestAnchoredPopupAtRect(
"Popup shown as desired width when max width is larger.",
/*anchorRect*/ new Rect(0, 0, 0, 0),
/*expectedPopupRect*/ new Rect(0, 0, 200, 300));
}
@Test
public void testCalcPopupRect_PreferredHorizontalOrientationCenter() {
mPreferredHorizontalOrientation = HorizontalOrientation.CENTER;
// E.left = (A.left + A.right) / 2 - w / 2 = (300 + 300) / 2 - 150 / 2 = 225
// E.top = A.bottom = 0
// E.right = E.left + w = 225 + 150 = 375
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Below and center the anchor rect.",
/*anchorRect*/ new Rect(300, 0, 300, 0),
/*expectedPopupRect*/ new Rect(225, 0, 375, 300));
// E.left = (A.left + A.right) / 2 - w / 2 = (200 + 400) / 2 - 150 / 2 = 225
// E.top = A.bottom = 0
// E.right = E.left + w = 225 + 150 = 375
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Use the center of the anchor rect width.",
/*anchorRect*/ new Rect(200, 0, 400, 0),
/*expectedPopupRect*/ new Rect(225, 0, 375, 300));
// E.left = (A.left + A.right) / 2 - w / 2 = (400 + 500) / 2 - 150 / 2 = 375
// E.top = A.top - h = 600 - 300 = 300
// E.right = E.left + w = 225 + 150 = 525
// E.bottom = A.top = 600
doTestAnchoredPopupAtRect(
"Above and center the anchor rect.",
/*anchorRect*/ new Rect(400, 600, 500, 600),
/*expectedPopupRect*/ new Rect(375, 300, 525, 600));
}
@Test
public void testCalcPopupRect_PreferredHorizontalOrientationLayoutDirection() {
mPreferredHorizontalOrientation = HorizontalOrientation.LAYOUT_DIRECTION;
LocalizationUtils.setRtlForTesting(false);
// E.left = A.left + w = 300
// E.top = A.bottom = 0
// E.right = E.left + w = 300 + 150 = 450
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Right of anchor rect.",
/*anchorRect*/ new Rect(300, 0, 300, 0),
/*expectedPopupRect*/ new Rect(300, 0, 450, 300));
LocalizationUtils.setRtlForTesting(true);
// E.left = A.left - w = 300 - 150 = 150
// E.top = A.bottom = 0
// E.right = E.left = 300
// E.bottom = A.bottom + h = 300
doTestAnchoredPopupAtRect(
"Left of anchor rect.",
/*anchorRect*/ new Rect(300, 0, 300, 0),
/*expectedPopupRect*/ new Rect(150, 0, 300, 300));
}
@Test
public void testCalcPopupRect_PreferredVerticalOrientation() {
mPreferredVerticalOrientation = VerticalOrientation.ABOVE;
// E.left = A.right = 0
// E.top = A.top - h = 300 - 300 = 0
// E.right = A.right + w = 150
// E.bottom = A.top = 300
doTestAnchoredPopupAtRect(
"Show above the anchor even when bottom has more space.",
/*anchorRect*/ new Rect(0, 300, 0, 300),
/*expectedPopupRect*/ new Rect(0, 0, 150, 300));
// E.left = A.right = 0
// E.top = A.bottom = 200
// E.right = A.right + w = 150
// E.bottom = A.bottom + h = 200 + 300 = 500
doTestAnchoredPopupAtRect(
"Show below the anchor since top does not have enough space.",
/*anchorRect*/ new Rect(0, 200, 0, 200),
/*expectedPopupRect*/ new Rect(0, 200, 150, 500));
mPreferredVerticalOrientation = VerticalOrientation.BELOW;
// E.left = A.right = 0
// E.top = A.bottom = 600
// E.right = A.right + w = 150
// E.bottom = A.bottom + h = 600 + 300 = 900
doTestAnchoredPopupAtRect(
"Show below the anchor even when top has more space.",
/*anchorRect*/ new Rect(0, 600, 0, 600),
/*expectedPopupRect*/ new Rect(0, 600, 150, 900));
// E.left = A.right = 0
// E.top = A.top - h = 800 - 300 = 500
// E.right = A.right + w = 150
// E.bottom = A.top = 800
doTestAnchoredPopupAtRect(
"Show above the anchor since bottom does not have enough space.",
/*anchorRect*/ new Rect(0, 800, 0, 800),
/*expectedPopupRect*/ new Rect(0, 500, 150, 800));
}
@Test
public void testCalcPopupRect_PreferCurrentOrientation() {
mPreferCurrentOrientation = true;
mCurrentPositionBelow = true;
mCurrentPositionToLeft = true;
// E.left = A.left - w = 200 - 150 = 50
// E.top = A.bottom = 200
// E.right = A.left = 200
// E.bottom = A.bottom + h = 700 + 300 = 1000
doTestAnchoredPopupAtRect(
"Anchored left bottom as preferred current orientation.",
/*anchorRect*/ new Rect(200, 700, 200, 700),
/*expectedPopupRect*/ new Rect(50, 700, 200, 1000));
// E.left = A.right = 200
// E.top = A.top - h = 700 - 300 = 400
// E.right = A.right + w = 200 + 150 = 350
// E.bottom = A.top = 700
doTestAnchoredPopupAtRect(
"Anchored top right due to limited space",
/*anchorRect*/ new Rect(100, 700, 200, 800),
/*expectedPopupRect*/ new Rect(200, 400, 350, 700));
}
@Test
public void testCalcPopupRect_HorizontalOverlap() {
mHorizontalOverlapAnchor = true;
// E.left = A.left = 0
// E.top = A.bottom = 200
// E.right = A.left + w = 0 + 150 = 150
// E.bottom = A.bottom + h = 200 + 300 = 500
doTestAnchoredPopupAtRect(
"Horizontal overlap with rect while position to the right.",
/*anchorRect*/ new Rect(0, 0, 100, 200),
/*expectedPopupRect*/ new Rect(0, 200, 150, 500));
// E.left = A.right - w = 600 - 150 = 450
// E.top = A.bottom = 200
// E.right = A.right = 600
// E.bottom = A.bottom + h = 200 + 300 = 500
doTestAnchoredPopupAtRect(
"Horizontal overlap with rect while position to the left.",
/*anchorRect*/ new Rect(400, 0, 600, 200),
/*expectedPopupRect*/ new Rect(450, 200, 600, 500));
}
@Test
public void testCalcPopupRect_VerticalOverlap() {
mVerticalOverlapAnchor = true;
// E.left = A.right = 100
// E.top = A.top = 400
// E.right = A.right + w = 100 + 150 = 250
// E.bottom = A.top + h = 400 + 300 = 700
doTestAnchoredPopupAtRect(
"Vertical overlap with rect while position below.",
/*anchorRect*/ new Rect(100, 400, 100, 600),
/*expectedPopupRect*/ new Rect(100, 400, 250, 700));
// E.left = A.right = 100
// E.top = A.bottom - h = 900 - 300 = 600
// E.right = A.right + w = 100 + 150 = 250
// E.bottom = A.bottom = 900
doTestAnchoredPopupAtRect(
"Vertical overlap with rect while position below.",
/*anchorRect*/ new Rect(100, 800, 100, 900),
/*expectedPopupRect*/ new Rect(100, 600, 250, 900));
}
@Test
public void testCalcPopupRect_SmartAnchorWithMaxWidth() {
mHorizontalOverlapAnchor = false;
mVerticalOverlapAnchor = true;
// E.left = max(window.left, A.left - w) = max(0, 100-150) = 0
// E.top = A.top = 200
// E.right = E.left + w = 0 + 150 = 150
// E.bottom = A.top + h = 200 + 300 = 500
doTestAnchoredPopupAtRect(
"Popup forced to horizontally overlap with anchor; "
+ "vertical with anchor is expected.",
/*anchorRect*/ new Rect(100, 200, 500, 800),
/*expectedPopupRect*/ new Rect(0, 200, 150, 500));
// Use smart anchor with max width to allow more width shown for the popup.
mSmartAnchorWithMaxWidth = true;
// E.left = A.right - w = 500 - 150 = 350
// E.top = A.bottom = 800
// E.right = A.right = 500
// E.bottom = min(window.bottom, A.bottom + h) = min(1000, 800 + 300) = 1000
doTestAnchoredPopupAtRect(
"Popup adjusted to show below the anchored rect, "
+ "while horizontally overlap with anchor but not vertically.",
/*anchorRect*/ new Rect(100, 200, 500, 800),
/*expectedPopupRect*/ new Rect(350, 800, 500, 1000));
}
@Test
public void calculateAnimationStyleStartTop() {
assertEquals(
"Position below right -> animate from start top.",
R.style.AnchoredPopupAnimStartTop,
AnchoredPopupWindow.calculateAnimationStyle(
/* isPositionBelow= */ true, /* isPositionToLeft= */ false));
}
@Test
public void calculateAnimationStyleStartBottom() {
assertEquals(
"Position above right -> animate from start bottom.",
R.style.AnchoredPopupAnimStartBottom,
AnchoredPopupWindow.calculateAnimationStyle(
/* isPositionBelow= */ false, /* isPositionToLeft= */ false));
}
@Test
public void calculateAnimationStyleEndTop() {
assertEquals(
"Position below left -> animate from end top.",
R.style.AnchoredPopupAnimEndTop,
AnchoredPopupWindow.calculateAnimationStyle(
/* isPositionBelow= */ true, /* isPositionToLeft= */ true));
}
@Test
public void calculateAnimationStyleEndBottom() {
assertEquals(
"Position above left -> animate from end bottom.",
R.style.AnchoredPopupAnimEndBottom,
AnchoredPopupWindow.calculateAnimationStyle(
/* isPositionBelow= */ false, /* isPositionToLeft= */ true));
}
@Test
public void setAnimateFromAnchor() {
// Set up for test case, so we have a mock popup window.
UiWidgetFactory mockFactory = mock(UiWidgetFactory.class);
UiWidgetFactory.setInstance(mockFactory);
PopupWindow mockPopup = mock(PopupWindow.class);
doReturn(mockPopup).when(mockFactory).createPopupWindow(any());
AnchoredPopupWindow popupWindow = createAnchorPopupWindow(0);
popupWindow.setAnimateFromAnchor(true);
popupWindow.showPopupWindow();
verify(mockPopup).setAnimationStyle(anyInt());
}
@Test
public void setAnimationStyleNotOverrideByAnimateFromAnchor() {
// Set up for test case, so we have a mock popup window.
UiWidgetFactory mockFactory = mock(UiWidgetFactory.class);
UiWidgetFactory.setInstance(mockFactory);
PopupWindow mockPopup = mock(PopupWindow.class);
doReturn(mockPopup).when(mockFactory).createPopupWindow(any());
AnchoredPopupWindow popupWindow = createAnchorPopupWindow(0);
popupWindow.setAnimationStyle(R.style.DropdownPopupWindow);
verify(mockPopup).setAnimationStyle(R.style.DropdownPopupWindow);
popupWindow.setAnimateFromAnchor(true);
popupWindow.showPopupWindow();
// setAnimationStyle should only called once, since #setAnimateFromAnchor is no-op.
verify(mockPopup, times(1)).setAnimationStyle(anyInt());
}
@Test
public void testVerySmallPopupsDoNotShow() {
UiWidgetFactory mockFactory = mock(UiWidgetFactory.class);
UiWidgetFactory.setInstance(mockFactory);
PopupWindow mockPopup = mock(PopupWindow.class);
when(mockPopup.isShowing()).thenReturn(false);
when(mockPopup.getBackground()).thenReturn(mock(Drawable.class));
when(mockFactory.createPopupWindow(any())).thenReturn(mockPopup);
View contentView = mock(ViewGroup.class);
when(contentView.getMeasuredHeight()).thenReturn(1);
when(contentView.getMeasuredWidth()).thenReturn(1);
when(mockPopup.getContentView()).thenReturn(contentView);
AnchoredPopupWindow anchoredPopupWindow =
createAnchorPopupWindow(DisplayMetrics.DENSITY_HIGH);
anchoredPopupWindow.show();
verify(mockPopup, never()).update(anyInt(), anyInt(), anyInt(), anyInt());
}
@Test
public void testWebContentsRectChangesUpdatesPopup() {
UiWidgetFactory mockFactory = mock(UiWidgetFactory.class);
UiWidgetFactory.setInstance(mockFactory);
PopupWindow mockPopup = mock(PopupWindow.class);
when(mockPopup.isShowing()).thenReturn(false);
when(mockPopup.getBackground()).thenReturn(mock(Drawable.class));
when(mockFactory.createPopupWindow(any())).thenReturn(mockPopup);
View contentView = mock(ViewGroup.class);
when(contentView.getMeasuredHeight()).thenReturn(200);
when(contentView.getMeasuredWidth()).thenReturn(800);
when(mockPopup.getContentView()).thenReturn(contentView);
View view = mock(View.class, Answers.RETURNS_DEEP_STUBS);
DisplayMetrics fakeMetrics = new DisplayMetrics();
fakeMetrics.density = 1;
when(view.getRootView().getResources().getDisplayMetrics()).thenReturn(fakeMetrics);
when(view.getRootView().isAttachedToWindow()).thenReturn(true);
RectProvider anchorRectProvider = new RectProvider(new Rect(0, 0, 1000, 1000));
RectProvider visibleWebContentsRectSupplier = new RectProvider(new Rect(0, 100, 1000, 900));
AnchoredPopupWindow anchoredPopupWindow =
new AnchoredPopupWindow(
mActivity,
view,
null,
mContentView,
anchorRectProvider,
visibleWebContentsRectSupplier);
anchoredPopupWindow.show();
verify(mockPopup, times(1)).update(anyInt(), anyInt(), anyInt(), anyInt());
clearInvocations(mockPopup);
// changing the rect should retrigger popup updates.
visibleWebContentsRectSupplier.setRect(new Rect(0, 100, 1000, 500));
verify(mockPopup, times(1)).update(anyInt(), anyInt(), anyInt(), anyInt());
}
private void setDefaultValueForAnchoredPopup() {
mPaddingX = 0;
mPaddingY = 0;
mMarginPx = 0;
mMaxWidthPx = 0;
mDesiredWidthPx = 0;
mPreferredHorizontalOrientation = HorizontalOrientation.MAX_AVAILABLE_SPACE;
mPreferredVerticalOrientation = VerticalOrientation.MAX_AVAILABLE_SPACE;
mCurrentPositionBelow = false;
mCurrentPositionToLeft = false;
mPreferCurrentOrientation = false;
mHorizontalOverlapAnchor = false;
mVerticalOverlapAnchor = false;
mSmartAnchorWithMaxWidth = false;
}
/**
* Test cases for {@link AnchoredPopupWindow.calculatePopupWindowSpec}, calculation is explained
* at each call site.
*/
private void doTestAnchoredPopupAtRect(String testCase, Rect anchoredRect, Rect expectedRect) {
PopupSpec popupSpec =
AnchoredPopupWindow.calculatePopupWindowSpec(
mWindowRect,
anchoredRect,
mContentView,
mRootWidth,
mPaddingX,
mPaddingY,
mMarginPx,
mMaxWidthPx,
mDesiredWidthPx,
mPreferredHorizontalOrientation,
mPreferredVerticalOrientation,
mCurrentPositionBelow,
mCurrentPositionToLeft,
mPreferCurrentOrientation,
mHorizontalOverlapAnchor,
mVerticalOverlapAnchor,
mSmartAnchorWithMaxWidth);
Rect popupRect = popupSpec.popupRect;
Assert.assertEquals(
String.format("PopupRect does not match expected Rect. Test case:<%s>", testCase),
expectedRect,
popupRect);
}
private AnchoredPopupWindow createAnchorPopupWindow(int density) {
View view = mock(View.class, Answers.RETURNS_DEEP_STUBS);
DisplayMetrics fakeMetrics = new DisplayMetrics();
fakeMetrics.density = density;
when(view.getRootView().getResources().getDisplayMetrics()).thenReturn(fakeMetrics);
when(view.getRootView().isAttachedToWindow()).thenReturn(true);
RectProvider provider = new RectProvider(new Rect(0, 0, 0, 0));
return new AnchoredPopupWindow(mActivity, view, null, mContentView, provider, null);
}
}