chromium/chrome/android/junit/src/org/chromium/chrome/browser/settings/SettingsActivityUnitTest.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.chrome.browser.settings;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.View;

import androidx.lifecycle.Lifecycle.State;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.core.app.ActivityScenario;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;

import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.profiles.ProfileManagerUtils;
import org.chromium.chrome.browser.settings.SettingsActivityUnitTest.ShadowProfileManagerUtils;
import org.chromium.components.browser_ui.settings.CustomDividerFragment;
import org.chromium.components.browser_ui.settings.PaddedItemDecorationWithDivider;

import java.util.concurrent.TimeoutException;

/** Unit tests for {@link SettingsActivity}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(shadows = ShadowProfileManagerUtils.class)
public class SettingsActivityUnitTest {
    /** Shadow class to bypass the real call to ProfileManagerUtils. */
    @Implements(ProfileManagerUtils.class)
    public static class ShadowProfileManagerUtils {
        @Implementation
        protected static void flushPersistentDataForAllProfiles() {}
    }

    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

    private ActivityScenario<SettingsActivity> mActivityScenario;
    private SettingsActivity mSettingsActivity;

    @Mock public ChromeBrowserInitializer mInitializer;
    @Mock public Profile mProfile;

    @Before
    public void setup() {
        ChromeBrowserInitializer.setForTesting(mInitializer);
        ProfileManager.setLastUsedProfileForTesting(mProfile);
    }

    @After
    public void tearDown() {
        if (mActivityScenario != null) {
            mActivityScenario.close();
            mActivityScenario = null;
        }
    }

    @Test
    public void testDefaultLaunchProcess() {
        launchSettingsActivity(TestSettingsFragment.class.getName());
        mActivityScenario.moveToState(State.CREATED);

        assertTrue(
                "SettingsActivity is using a wrong fragment.",
                mSettingsActivity.getMainFragment() instanceof TestSettingsFragment);
    }

    @Test
    public void testBackPress() throws TimeoutException {
        launchSettingsActivity(TestSettingsFragment.class.getName());
        assertTrue(
                "SettingsActivity is using a wrong fragment.",
                mSettingsActivity.getMainFragment() instanceof TestSettingsFragment);
        TestSettingsFragment mainFragment =
                (TestSettingsFragment) mSettingsActivity.getMainFragment();
        mainFragment.getHandleBackPressChangedSupplier().set(true);
        Assert.assertTrue(
                "TestSettingsFragment will handle back press",
                mSettingsActivity.getOnBackPressedDispatcher().hasEnabledCallbacks());

        // Simulate back press.
        ThreadUtils.runOnUiThreadBlocking(
                mSettingsActivity.getOnBackPressedDispatcher()::onBackPressed);
        mainFragment.getBackPressCallback().waitForOnly();

        mainFragment.getHandleBackPressChangedSupplier().set(false);
        Assert.assertFalse(
                "TestSettingsFragment will not handle back press",
                mSettingsActivity.getOnBackPressedDispatcher().hasEnabledCallbacks());
    }

    @Test
    @Config(qualifiers = "w720dp-h1024dp")
    public void addPaddingToContentOnWideDisplay() {
        launchSettingsActivity(TestSettingsFragment.class.getName());
        mActivityScenario.moveToState(State.CREATED);
        mActivityScenario.moveToState(State.STARTED);
        mActivityScenario.moveToState(State.RESUMED);

        RecyclerView recyclerView = mSettingsActivity.findViewById(R.id.recycler_view);
        PaddedItemDecorationWithDivider decoration = getPaddedDecoration(recyclerView);
        assertNotNull("PaddedItemDecorationWithDivider should exists.", decoration);
        int parentPadding =
                60; // (720 - UiConfig.WIDE_DISPLAY_STYLE_MIN_WIDTH_DP) / 2 = (720 - 600) / 2
        int itemOffset = decoration.getItemOffsetForTesting();
        assertEquals("Item offset is wrong.", parentPadding, itemOffset);
        assertEquals("Divider start padding is wrong.", 0, decoration.getDividerPaddingStart());
        assertEquals("Divider end padding is wrong.", 0, decoration.getDividerPaddingEnd());
    }

    @Test
    @Config(qualifiers = "w320dp-h1024dp")
    public void addPaddingToContentOnNarrowDisplay() {
        launchSettingsActivity(TestSettingsFragment.class.getName());
        mActivityScenario.moveToState(State.CREATED);
        mActivityScenario.moveToState(State.STARTED);
        mActivityScenario.moveToState(State.RESUMED);

        RecyclerView recyclerView = mSettingsActivity.findViewById(R.id.recycler_view);
        PaddedItemDecorationWithDivider decoration = getPaddedDecoration(recyclerView);
        assertNotNull("PaddedItemDecorationWithDivider should exists.", decoration);
        int itemOffset = decoration.getItemOffsetForTesting();
        assertEquals("Item offset is wrong.", 0, itemOffset);
        assertEquals("Divider start padding is wrong.", 0, decoration.getDividerPaddingStart());
        assertEquals("Divider end padding is wrong.", 0, decoration.getDividerPaddingEnd());
    }

    @Test
    @Config(qualifiers = "w720dp-h1024dp")
    public void addPaddingToContentOnWideDisplay_NoDivider() {
        CustomDividerTestSettingsFragment.sHasDivider = false;
        launchSettingsActivity(CustomDividerTestSettingsFragment.class.getName());
        mActivityScenario.moveToState(State.CREATED);
        mActivityScenario.moveToState(State.STARTED);
        mActivityScenario.moveToState(State.RESUMED);

        RecyclerView recyclerView = mSettingsActivity.findViewById(R.id.recycler_view);
        PaddedItemDecorationWithDivider decoration = getPaddedDecoration(recyclerView);
        assertNotNull("PaddedItemDecorationWithDivider should exists.", decoration);
        int parentPadding =
                60; // (720 - UiConfig.WIDE_DISPLAY_STYLE_MIN_WIDTH_DP) / 2 = (720 - 600) / 2

        int itemOffset = decoration.getItemOffsetForTesting();
        assertEquals("Item offset is wrong.", parentPadding, itemOffset);
        assertEquals(
                "Divider start padding should not be set.", 0, decoration.getDividerPaddingStart());
        assertEquals(
                "Divider end padding should not be set.", 0, decoration.getDividerPaddingEnd());
    }

    @Test
    @Config(qualifiers = "w720dp-h1024dp")
    public void addPaddingToContentOnWideDisplay_HasCustomDivider() {
        CustomDividerTestSettingsFragment.sHasDivider = true;

        launchSettingsActivity(CustomDividerTestSettingsFragment.class.getName());
        mActivityScenario.moveToState(State.CREATED);
        mActivityScenario.moveToState(State.STARTED);
        mActivityScenario.moveToState(State.RESUMED);

        RecyclerView recyclerView = mSettingsActivity.findViewById(R.id.recycler_view);
        PaddedItemDecorationWithDivider decoration = getPaddedDecoration(recyclerView);

        assertNotNull("PaddedItemDecorationWithDivider should exists.", decoration);
        int parentPadding =
                60; // (720 - UiConfig.WIDE_DISPLAY_STYLE_MIN_WIDTH_DP) / 2 = (720 - 600) / 2
        int itemOffset = decoration.getItemOffsetForTesting();
        assertEquals("Item offset is wrong.", parentPadding, itemOffset);
        assertEquals(
                "Divider start padding is wrong.",
                CustomDividerTestSettingsFragment.DIVIDER_START_PADDING,
                decoration.getDividerPaddingStart());
        assertEquals(
                "Divider end padding is wrong.",
                CustomDividerTestSettingsFragment.DIVIDER_END_PADDING,
                decoration.getDividerPaddingEnd());

        // simulate onDraw() call and verify padding
        RecyclerView.State state = new RecyclerView.State();
        decoration.onDraw(new Canvas(), recyclerView, state);
        for (int index = 0; index < recyclerView.getChildCount(); index++) {
            View view = recyclerView.getChildAt(index);
            Rect outRect = new Rect();
            decoration.getItemOffsets(outRect, view, recyclerView, state);
            assertEquals("Recycler view item offset padding is wrong", parentPadding, outRect.left);
            assertEquals("Recycler view item end offset is wrong", parentPadding, outRect.right);
        }
    }

    private void launchSettingsActivity(String fragmentName) {
        assert mActivityScenario == null : "Should be called once per test.";
        Intent intent = new Intent();
        intent.setClass(ContextUtils.getApplicationContext(), SettingsActivity.class);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, fragmentName);
        mActivityScenario = ActivityScenario.launch(intent);
        mActivityScenario.onActivity(activity -> mSettingsActivity = activity);
    }

    private PaddedItemDecorationWithDivider getPaddedDecoration(RecyclerView recyclerView) {
        for (int i = 0; i < recyclerView.getItemDecorationCount(); ++i) {
            if (recyclerView.getItemDecorationAt(i) instanceof PaddedItemDecorationWithDivider) {
                return (PaddedItemDecorationWithDivider) recyclerView.getItemDecorationAt(i);
            }
        }
        return null;
    }

    /** Class that override the divider behavior. */
    public static class CustomDividerTestSettingsFragment extends TestSettingsFragment
            implements CustomDividerFragment {
        static final int DIVIDER_START_PADDING = 10;
        static final int DIVIDER_END_PADDING = 15;

        public static boolean sHasDivider;

        public CustomDividerTestSettingsFragment() {}

        @Override
        public boolean hasDivider() {
            return sHasDivider;
        }

        @Override
        public int getDividerStartPadding() {
            return DIVIDER_START_PADDING;
        }

        @Override
        public int getDividerEndPadding() {
            return DIVIDER_END_PADDING;
        }
    }
}