chromium/ui/android/junit/src/org/chromium/ui/widget/ViewRectProviderTest.java

// Copyright 2022 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 android.app.Activity;
import android.graphics.Rect;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnAttachStateChangeListener;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowView;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.ui.widget.RectProvider.Observer;
import org.chromium.ui.widget.ViewRectProviderTest.MyShadowView;

import java.util.Optional;

/** Unit tests for {@link ViewRectProvider} .*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(shadows = MyShadowView.class)
public class ViewRectProviderTest {
    private static final int WIDTH = 600;
    private static final int HEIGHT = 800;

    /** Custom ShadowView which includes setter for {@link View#isShown}. **/
    @Implements(View.class)
    public static class MyShadowView extends ShadowView {
        boolean mIsShown;

        /** Empty ctor required for shadow classes. */
        public MyShadowView() {}

        @Implementation
        protected boolean isShown() {
            return mIsShown;
        }

        @Implementation
        protected void getLocationInWindow(int[] outLocation) {
            outLocation[0] = this.realView.getLeft();
            outLocation[1] = this.realView.getTop();
        }

        void setIsShown(boolean isShown) {
            mIsShown = isShown;
        }
    }

    private ViewRectProvider mViewRectProvider;
    private FrameLayout mRootView;
    private View mView;
    private MyShadowView mShadowView;
    private Activity mActivity;
    private RectProvider.Observer mObserver;

    private CallbackHelper mOnRectChangeCallback;
    private CallbackHelper mOnRectHideCallback;

    @Before
    public void setup() {
        mActivity = Robolectric.buildActivity(Activity.class).get();
        mRootView = new FrameLayout(mActivity);
        mActivity.setContentView(mRootView);

        mView = new View(mActivity);

        mRootView.addView(
                mView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        mViewRectProvider = new ViewRectProvider(mView);

        mOnRectChangeCallback = new CallbackHelper();
        mOnRectHideCallback = new CallbackHelper();
        mObserver =
                new Observer() {
                    @Override
                    public void onRectChanged() {
                        mOnRectChangeCallback.notifyCalled();
                    }

                    @Override
                    public void onRectHidden() {
                        mOnRectHideCallback.notifyCalled();
                    }
                };

        View rootView = mRootView.getRootView();
        rootView.measure(
                MeasureSpec.makeMeasureSpec(WIDTH, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(HEIGHT, MeasureSpec.EXACTLY));
        rootView.layout(0, 0, WIDTH, HEIGHT);

        mShadowView = Shadow.extract(mView);
        mShadowView.setIsShown(true);

        mViewRectProvider.startObserving(mObserver);
    }

    @Test
    public void testProviderViewBound() {
        assertRectMatch(0, 0, WIDTH, HEIGHT);
    }

    @Test
    public void testOnRectHidden() {
        mShadowView.setIsShown(false);

        int expectedCounts = 0;
        mView.getViewTreeObserver().dispatchOnGlobalLayout();
        Assert.assertEquals(
                "#onGlobalLayout should call #onRectHidden.",
                ++expectedCounts,
                mOnRectHideCallback.getCallCount());

        mView.getViewTreeObserver().dispatchOnPreDraw();
        Assert.assertEquals(
                "#onPreDraw should call #onRectHidden.",
                ++expectedCounts,
                mOnRectHideCallback.getCallCount());

        Optional<OnAttachStateChangeListener> listener =
                mShadowView.getOnAttachStateChangeListeners().stream().findFirst();
        Assert.assertTrue(listener.isPresent());
        listener.get().onViewDetachedFromWindow(mView);
        Assert.assertEquals(
                "#onViewDetachedFromWindow should call #onRectHidden.",
                ++expectedCounts,
                mOnRectHideCallback.getCallCount());
    }

    @Test
    public void testOnRectChanged() {
        int expectedCounts = 0;

        mView.layout(0, 0, WIDTH, HEIGHT);
        mView.getViewTreeObserver().dispatchOnPreDraw();
        Assert.assertEquals(
                "View does not changes its bound. Should not trigger #onRectChanged.",
                expectedCounts,
                mOnRectChangeCallback.getCallCount());
        mView.layout(0, 0, 100, 100);
        mView.getViewTreeObserver().dispatchOnPreDraw();
        Assert.assertEquals(
                "View changing its bound should trigger #onRectChanged.",
                ++expectedCounts,
                mOnRectChangeCallback.getCallCount());
        assertRectMatch(0, 0, 100, 100);

        mView.layout(1, 1, 101, 101);
        mView.getViewTreeObserver().dispatchOnPreDraw();
        Assert.assertEquals(
                "View changing its position on screen should trigger #onRectChanged.",
                ++expectedCounts,
                mOnRectChangeCallback.getCallCount());
        assertRectMatch(1, 1, 101, 101);

        mViewRectProvider.setInsetPx(new Rect(0, 0, 0, 0));
        Assert.assertEquals(
                "Setting same view inset should not trigger #onRectChanged.",
                expectedCounts,
                mOnRectChangeCallback.getCallCount());
        mViewRectProvider.setInsetPx(new Rect(0, 0, 0, 1));
        Assert.assertEquals(
                "Setting different inset on ViewRectProvider should trigger #onRectChanged.",
                ++expectedCounts,
                mOnRectChangeCallback.getCallCount());
        assertRectMatch(1, 1, 101, 100);

        mViewRectProvider.setIncludePadding(false);
        Assert.assertEquals(
                "Setting same ViewProvider#setIncludePadding should not trigger #onRectChanged.",
                expectedCounts,
                mOnRectChangeCallback.getCallCount());
        mViewRectProvider.setIncludePadding(true);
        Assert.assertEquals(
                "Setting different ViewProvider#setIncludePadding should trigger #onRectChanged.",
                ++expectedCounts,
                mOnRectChangeCallback.getCallCount());
    }

    private void assertRectMatch(int left, int top, int right, int bottom) {
        final Rect expectedRect = new Rect(left, top, right, bottom);
        Assert.assertEquals("Rect does not match.", expectedRect, mViewRectProvider.getRect());
    }
}