diff --git a/espresso/CHANGELOG.md b/espresso/CHANGELOG.md index 110c0a51f..e411153da 100644 --- a/espresso/CHANGELOG.md +++ b/espresso/CHANGELOG.md @@ -20,6 +20,7 @@ The following artifacts were released: * Don't suppress AppNotIdleException if dumpThreadStates throws. * Remove Espresso.onIdle tracing * Fix NullPointerException in UiControllerImpl. +* Fix `isDisplayingAtLeast` matcher to factor in the scale of ancestor views. **New Features** diff --git a/espresso/core/java/androidx/test/espresso/matcher/ViewMatchers.java b/espresso/core/java/androidx/test/espresso/matcher/ViewMatchers.java index 4246e8d2f..bbaf2ed9c 100644 --- a/espresso/core/java/androidx/test/espresso/matcher/ViewMatchers.java +++ b/espresso/core/java/androidx/test/espresso/matcher/ViewMatchers.java @@ -994,16 +994,23 @@ protected boolean matchesSafely(View view, Description mismatchDescription) { Rect screen = getScreenWithoutStatusBarActionBar(view); - float viewHeight = (view.getHeight() > screen.height()) ? screen.height() : view.getHeight(); - float viewWidth = (view.getWidth() > screen.width()) ? screen.width() : view.getWidth(); + float totalScaleX = view.getScaleX(); + float totalScaleY = view.getScaleY(); + ViewParent parent = view.getParent(); + while (parent instanceof View) { + View parentView = (View) parent; + totalScaleX *= parentView.getScaleX(); + totalScaleY *= parentView.getScaleY(); + parent = parentView.getParent(); + } - // factor in the View's scaleX and scaleY properties. - viewHeight = Math.min(view.getHeight() * Math.abs(view.getScaleY()), screen.height()); - viewWidth = Math.min(view.getWidth() * Math.abs(view.getScaleX()), screen.width()); + // factor in the View and its ancestors' scaleX and scaleY properties. + float viewHeight = Math.min(view.getHeight() * Math.abs(totalScaleY), screen.height()); + float viewWidth = Math.min(view.getWidth() * Math.abs(totalScaleX), screen.width()); double maxArea = viewHeight * viewWidth; double visibleArea = visibleParts.height() * visibleParts.width(); - int displayedPercentage = (int) ((visibleArea / maxArea) * 100); + int displayedPercentage = maxArea > 0 ? (int) ((visibleArea / maxArea) * 100) : 0; if (displayedPercentage < areaPercentage) { mismatchDescription diff --git a/espresso/core/javatests/androidx/test/espresso/matcher/ViewMatchers1Test.java b/espresso/core/javatests/androidx/test/espresso/matcher/ViewMatchers1Test.java index cc258f443..ed075236e 100644 --- a/espresso/core/javatests/androidx/test/espresso/matcher/ViewMatchers1Test.java +++ b/espresso/core/javatests/androidx/test/espresso/matcher/ViewMatchers1Test.java @@ -65,6 +65,7 @@ import android.widget.CheckBox; import android.widget.Checkable; import android.widget.CheckedTextView; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.RelativeLayout; @@ -767,6 +768,32 @@ public void isDisplayingAtLeastTest() { assertTrue(isDisplayingAtLeast(20).matches(view)); } + @Test + public void isDisplayingAtLeast_withParentScale() { + GlobalVisibleRectProvider providerMock = mock(GlobalVisibleRectProvider.class); + FrameLayout parent = new FrameLayout(context); + View child = new GlobalVisibleRectTestView(context, providerMock); + parent.addView(child); + + // Set the view to be 100x100 with scale(0.5, -0.5): 2,500 pixels + child.setVisibility(View.VISIBLE); + child.layout(0, 0, 100, 100); + child.setScaleX(0.5f); + parent.setScaleY(-0.5f); + when(providerMock.get(any(), any())) + .then( + (Answer) + invocation -> { + // Set the output rectangle to 40x40: 1,600 pixels + Rect argRect = invocation.getArgument(0); + argRect.set(0, 0, 40, 40); + return true; + }); + + assertFalse(isDisplayingAtLeast(70).matches(child)); + assertTrue(isDisplayingAtLeast(60).matches(child)); + } + @Test public void isDisplayingAtLeast_description() { assertThat(