chromium/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomViewTest.java

// Copyright 2023 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.components.browser_ui.accessibility;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;

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.Mockito.when;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.SeekBar;
import android.widget.TextView;

import androidx.test.filters.SmallTest;

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseActivityTestRule;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.JniMocker;
import org.chromium.content.browser.HostZoomMapImpl;
import org.chromium.content.browser.HostZoomMapImplJni;
import org.chromium.content_public.browser.BrowserContextHandle;
import org.chromium.content_public.browser.ContentFeatureList;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.test.util.BlankUiTestActivity;

/** Unit tests for the PageZoom view and view binder. */
@RunWith(BaseJUnit4ClassRunner.class)
@EnableFeatures({ContentFeatureList.ACCESSIBILITY_PAGE_ZOOM})
@DisableFeatures({
    ContentFeatureList.SMART_ZOOM,
    ContentFeatureList.ACCESSIBILITY_PAGE_ZOOM_ENHANCEMENTS
})
@Batch(Batch.PER_CLASS)
public class PageZoomViewTest {
    @ClassRule
    public static BaseActivityTestRule<BlankUiTestActivity> sActivityTestRule =
            new BaseActivityTestRule<>(BlankUiTestActivity.class);

    private static Activity sActivity;
    private static ViewGroup sContentView;

    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private PageZoomCoordinatorDelegate mDelegate;
    @Mock private HostZoomMapImpl.Natives mHostZoomMapJniMock;
    @Mock private PageZoomMetrics.Natives mPageZoomMetricsJniMock;
    @Mock private BrowserContextHandle mBrowserContextHandle;
    @Mock private WebContents mWebContents;

    private PageZoomCoordinator mCoordinator;
    private View mPageZoomView;

    @BeforeClass
    public static void setupSuite() {
        sActivityTestRule.launchActivity(null);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    sActivity = sActivityTestRule.getActivity();
                    sContentView = new FrameLayout(sActivity);
                    sActivity.setContentView(sContentView);
                });
    }

    @AfterClass
    public static void tearDownSuite() {
        sActivity = null;
        sContentView = null;
    }

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mJniMocker.mock(HostZoomMapImplJni.TEST_HOOKS, mHostZoomMapJniMock);
        mJniMocker.mock(PageZoomMetricsJni.TEST_HOOKS, mPageZoomMetricsJniMock);
        when(mHostZoomMapJniMock.getDefaultZoomLevel(any())).thenReturn(0.0);
        when(mHostZoomMapJniMock.getZoomLevel(any())).thenReturn(0.0);

        mDelegate =
                new PageZoomCoordinatorDelegate() {
                    @Override
                    public View getZoomControlView() {
                        return mPageZoomView;
                    }

                    @Override
                    public BrowserContextHandle getBrowserContextHandle() {
                        return mBrowserContextHandle;
                    }
                };

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    sContentView.removeAllViews();
                    mPageZoomView =
                            LayoutInflater.from(sActivity)
                                    .inflate(R.layout.page_zoom_view, sContentView, false);
                    sContentView.addView(mPageZoomView);

                    mCoordinator = new PageZoomCoordinator(mDelegate);
                    mCoordinator.show(mWebContents);
                });
    }

    // Test cases.

    @Test
    @SmallTest
    public void testSetup() {
        // Verify that the view has been drawn.
        assertEquals(View.VISIBLE, mPageZoomView.getVisibility());
        assertEquals(
                View.VISIBLE,
                mPageZoomView.findViewById(R.id.page_zoom_current_zoom_level).getVisibility());
        assertEquals(
                View.VISIBLE,
                mPageZoomView.findViewById(R.id.page_zoom_view_container).getVisibility());
        assertEquals(
                View.VISIBLE,
                mPageZoomView.findViewById(R.id.page_zoom_decrease_zoom_button).getVisibility());
        assertEquals(
                View.VISIBLE, mPageZoomView.findViewById(R.id.page_zoom_slider).getVisibility());
        assertEquals(
                View.VISIBLE,
                mPageZoomView.findViewById(R.id.page_zoom_increase_zoom_button).getVisibility());

        // The 'Reset' button and divider should not be visible by default.
        assertEquals(
                View.GONE,
                mPageZoomView.findViewById(R.id.page_zoom_reset_divider).getVisibility());
        assertEquals(
                View.GONE,
                mPageZoomView.findViewById(R.id.page_zoom_reset_zoom_button).getVisibility());
    }

    @Test
    @SmallTest
    public void testContent() {
        // Verify that all content is correct.
        assertFalse(
                mPageZoomView
                        .findViewById(R.id.page_zoom_view_container)
                        .isImportantForAccessibility());
        assertTextContent("100");
        assertTrue(
                mPageZoomView
                        .findViewById(R.id.page_zoom_decrease_zoom_button)
                        .getContentDescription()
                        .equals("Decrease zoom"));
        assertTrue(
                mPageZoomView
                        .findViewById(R.id.page_zoom_increase_zoom_button)
                        .getContentDescription()
                        .equals("Increase zoom"));
    }

    @Test
    @SmallTest
    public void testDecreaseButton() {
        assertEquals(
                50, ((SeekBar) mPageZoomView.findViewById(R.id.page_zoom_slider)).getProgress());
        assertViewState("100", true, true);

        var histogramWatcher =
                HistogramWatcher.newBuilder()
                        .expectBooleanRecord(
                                PageZoomUma.PAGE_ZOOM_APP_MENU_SLIDER_ZOOM_LEVEL_CHANGED_HISTOGRAM,
                                true)
                        .expectIntRecord(
                                PageZoomUma.PAGE_ZOOM_APP_MENU_SLIDER_ZOOM_LEVEL_VALUE_HISTOGRAM,
                                90)
                        .build();

        onView(withId(R.id.page_zoom_decrease_zoom_button)).perform(click());
        assertEquals(
                40, ((SeekBar) mPageZoomView.findViewById(R.id.page_zoom_slider)).getProgress());
        assertViewState("90", true, true);

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mCoordinator.hide();
                });

        histogramWatcher.assertExpected();
    }

    @Test
    @SmallTest
    public void testIncreaseButton() {
        assertEquals(
                50, ((SeekBar) mPageZoomView.findViewById(R.id.page_zoom_slider)).getProgress());
        assertViewState("100", true, true);

        var histogramWatcher =
                HistogramWatcher.newBuilder()
                        .expectBooleanRecord(
                                PageZoomUma.PAGE_ZOOM_APP_MENU_SLIDER_ZOOM_LEVEL_CHANGED_HISTOGRAM,
                                true)
                        .expectIntRecord(
                                PageZoomUma.PAGE_ZOOM_APP_MENU_SLIDER_ZOOM_LEVEL_VALUE_HISTOGRAM,
                                110)
                        .build();

        onView(withId(R.id.page_zoom_increase_zoom_button)).perform(click());
        assertEquals(
                60, ((SeekBar) mPageZoomView.findViewById(R.id.page_zoom_slider)).getProgress());
        assertViewState("110", true, true);

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mCoordinator.hide();
                });

        histogramWatcher.assertExpected();
    }

    @Test
    @SmallTest
    @EnableFeatures({ContentFeatureList.ACCESSIBILITY_PAGE_ZOOM_ENHANCEMENTS})
    public void testResetButton() {
        assertEquals(
                50, ((SeekBar) mPageZoomView.findViewById(R.id.page_zoom_slider)).getProgress());
        assertViewState("100", true, true);

        onView(withId(R.id.page_zoom_increase_zoom_button)).perform(click());
        assertViewState("110", true, true);

        onView(withId(R.id.page_zoom_reset_zoom_button)).perform(click());
        assertViewState("100", true, true);
    }

    // Helper methods for simple checks.

    private void assertViewState(
            String expectedText,
            boolean expectedDecreaseButtonEnabled,
            boolean expectedIncreaseButtonEnabled) {
        assertTextContent(expectedText);
        assertButtonStates(expectedDecreaseButtonEnabled, expectedIncreaseButtonEnabled);
    }

    private void assertTextContent(String expected) {
        assertTrue(
                ((TextView) mPageZoomView.findViewById(R.id.page_zoom_current_zoom_level))
                        .getText()
                        .equals(expected + " %"));
        assertTrue(
                mPageZoomView
                        .findViewById(R.id.page_zoom_current_zoom_level)
                        .getContentDescription()
                        .equals("Current zoom is " + expected + " %"));
    }

    private void assertButtonStates(boolean decreaseExpected, boolean increaseExpected) {
        assertEquals(
                decreaseExpected,
                mPageZoomView.findViewById(R.id.page_zoom_decrease_zoom_button).isEnabled());
        assertEquals(
                increaseExpected,
                mPageZoomView.findViewById(R.id.page_zoom_increase_zoom_button).isEnabled());
    }
}