chromium/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/DualControlLayoutTest.java

// Copyright 2015 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.widget;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.Space;

import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.components.browser_ui.widget.DualControlLayout.ButtonType;
import org.chromium.components.browser_ui.widget.DualControlLayout.DualControlLayoutAlignment;
import org.chromium.components.browser_ui.widget.test.R;

/** Tests for DualControlLayout. */
@RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
public class DualControlLayoutTest {
    private static final int PRIMARY_HEIGHT = 16;
    private static final int SECONDARY_HEIGHT = 8;
    private static final int STACKED_MARGIN = 4;
    private static final int INFOBAR_WIDTH = 3200;

    private static final int PADDING_LEFT = 8;
    private static final int PADDING_TOP = 16;
    private static final int PADDING_RIGHT = 32;
    private static final int PADDING_BOTTOM = 64;

    private int mTinyControlWidth;
    private Context mContext;

    @Before
    public void setUp() {
        mContext = InstrumentationRegistry.getTargetContext();
        mTinyControlWidth = INFOBAR_WIDTH / 4;
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testAlignSideBySide() {
        runLayoutTest(DualControlLayoutAlignment.START, false, false, false);
        runLayoutTest(DualControlLayoutAlignment.START, false, true, false);
        runLayoutTest(DualControlLayoutAlignment.START, true, false, false);
        runLayoutTest(DualControlLayoutAlignment.START, true, true, false);

        runLayoutTest(DualControlLayoutAlignment.APART, false, false, false);
        runLayoutTest(DualControlLayoutAlignment.APART, false, true, false);
        runLayoutTest(DualControlLayoutAlignment.APART, true, false, false);
        runLayoutTest(DualControlLayoutAlignment.APART, true, true, false);

        runLayoutTest(DualControlLayoutAlignment.END, false, false, false);
        runLayoutTest(DualControlLayoutAlignment.END, false, true, false);
        runLayoutTest(DualControlLayoutAlignment.END, true, false, false);
        runLayoutTest(DualControlLayoutAlignment.END, true, true, false);

        // Test the padding.
        runLayoutTest(DualControlLayoutAlignment.START, false, false, true);
        runLayoutTest(DualControlLayoutAlignment.START, false, true, true);
        runLayoutTest(DualControlLayoutAlignment.START, true, false, true);
        runLayoutTest(DualControlLayoutAlignment.START, true, true, true);

        runLayoutTest(DualControlLayoutAlignment.APART, false, false, true);
        runLayoutTest(DualControlLayoutAlignment.APART, false, true, true);
        runLayoutTest(DualControlLayoutAlignment.APART, true, false, true);
        runLayoutTest(DualControlLayoutAlignment.APART, true, true, true);

        runLayoutTest(DualControlLayoutAlignment.END, false, false, true);
        runLayoutTest(DualControlLayoutAlignment.END, false, true, true);
        runLayoutTest(DualControlLayoutAlignment.END, true, false, true);
        runLayoutTest(DualControlLayoutAlignment.END, true, true, true);
    }

    /** Lays out two controls that fit on the same line. */
    private void runLayoutTest(
            int alignment, boolean isRtl, boolean addSecondView, boolean addPadding) {
        DualControlLayout layout = new DualControlLayout(mContext, null);
        if (addPadding) layout.setPadding(PADDING_LEFT, PADDING_TOP, PADDING_RIGHT, PADDING_BOTTOM);
        layout.setAlignment(alignment);
        layout.setLayoutDirection(isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
        layout.setLayoutParams(
                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));

        View primary = new Space(mContext);
        primary.setMinimumWidth(mTinyControlWidth);
        primary.setMinimumHeight(PRIMARY_HEIGHT);
        layout.addView(primary);

        View secondary = null;
        if (addSecondView) {
            secondary = new Space(mContext);
            secondary.setMinimumWidth(mTinyControlWidth);
            secondary.setMinimumHeight(SECONDARY_HEIGHT);
            layout.addView(secondary);
        }

        // Trigger the measurement & layout algorithms.
        int parentWidthSpec = MeasureSpec.makeMeasureSpec(INFOBAR_WIDTH, MeasureSpec.EXACTLY);
        int parentHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        layout.measure(parentWidthSpec, parentHeightSpec);
        layout.layout(0, 0, layout.getMeasuredWidth(), layout.getMeasuredHeight());

        // Confirm that the primary View is in the correct place.
        if ((isRtl && alignment == DualControlLayoutAlignment.START)
                || (!isRtl
                        && (alignment == DualControlLayoutAlignment.APART
                                || alignment
                                        == DualControlLayout.DualControlLayoutAlignment.END))) {
            int expectedRight = INFOBAR_WIDTH - (addPadding ? PADDING_RIGHT : 0);
            Assert.assertEquals(
                    "Primary should be on the right.", expectedRight, primary.getRight());
        } else {
            int expectedLeft = addPadding ? PADDING_LEFT : 0;
            Assert.assertEquals("Primary should be on the left.", expectedLeft, primary.getLeft());
        }
        int expectedTop = addPadding ? PADDING_TOP : 0;
        int expectedBottom = expectedTop + PRIMARY_HEIGHT;
        Assert.assertEquals("Primary top in wrong location", expectedTop, primary.getTop());
        Assert.assertEquals(
                "Primary bottom in wrong location", expectedBottom, primary.getBottom());
        Assert.assertEquals(mTinyControlWidth, primary.getMeasuredWidth());
        Assert.assertEquals(PRIMARY_HEIGHT, primary.getMeasuredHeight());
        Assert.assertNotEquals(primary.getLeft(), primary.getRight());

        // Confirm that the secondary View is in the correct place.
        if (secondary != null) {
            Assert.assertEquals(mTinyControlWidth, secondary.getMeasuredWidth());
            Assert.assertEquals(SECONDARY_HEIGHT, secondary.getMeasuredHeight());
            Assert.assertNotEquals(secondary.getLeft(), secondary.getRight());
            if (alignment == DualControlLayoutAlignment.START) {
                if (isRtl) {
                    // Secondary View is immediately to the left of the parent.
                    Assert.assertTrue(secondary.getRight() < primary.getLeft());
                    Assert.assertNotEquals(0, secondary.getLeft());
                } else {
                    // Secondary View is immediately to the right of the parent.
                    Assert.assertTrue(primary.getRight() < secondary.getLeft());
                    Assert.assertNotEquals(INFOBAR_WIDTH, secondary.getRight());
                }
            } else if (alignment == DualControlLayoutAlignment.APART) {
                if (isRtl) {
                    // Secondary View is on the far right.
                    Assert.assertTrue(primary.getRight() < secondary.getLeft());
                    int expectedRight = INFOBAR_WIDTH - (addPadding ? PADDING_RIGHT : 0);
                    Assert.assertEquals(expectedRight, secondary.getRight());
                } else {
                    // Secondary View is on the far left.
                    Assert.assertTrue(secondary.getRight() < primary.getLeft());
                    int expectedLeft = addPadding ? PADDING_LEFT : 0;
                    Assert.assertEquals(expectedLeft, secondary.getLeft());
                }
            } else {
                Assert.assertEquals(DualControlLayoutAlignment.END, alignment);
                if (isRtl) {
                    // Secondary View is immediately to the right of the parent.
                    Assert.assertTrue(primary.getRight() < secondary.getLeft());
                    Assert.assertNotEquals(INFOBAR_WIDTH, secondary.getRight());
                } else {
                    // Secondary View is immediately to the left of the parent.
                    Assert.assertTrue(secondary.getRight() < primary.getLeft());
                    Assert.assertNotEquals(0, secondary.getLeft());
                }
            }

            // Confirm that the secondary View is centered with respect to the first.
            int primaryCenter = (primary.getTop() + primary.getBottom()) / 2;
            int secondaryCenter = (secondary.getTop() + secondary.getBottom()) / 2;
            Assert.assertEquals(primaryCenter, secondaryCenter);
        }

        int expectedLayoutHeight =
                primary.getMeasuredHeight() + (addPadding ? PADDING_TOP + PADDING_BOTTOM : 0);
        Assert.assertEquals(expectedLayoutHeight, layout.getMeasuredHeight());
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testStacked() {
        runStackedLayoutTest(DualControlLayoutAlignment.START, false, false);
        runStackedLayoutTest(DualControlLayoutAlignment.START, true, false);
        runStackedLayoutTest(DualControlLayoutAlignment.APART, false, false);
        runStackedLayoutTest(DualControlLayoutAlignment.APART, true, false);
        runStackedLayoutTest(DualControlLayoutAlignment.END, false, false);
        runStackedLayoutTest(DualControlLayoutAlignment.END, true, false);

        // Test the padding.
        runStackedLayoutTest(DualControlLayoutAlignment.START, false, true);
        runStackedLayoutTest(DualControlLayoutAlignment.START, true, true);
        runStackedLayoutTest(DualControlLayoutAlignment.APART, false, true);
        runStackedLayoutTest(DualControlLayoutAlignment.APART, true, true);
        runStackedLayoutTest(DualControlLayoutAlignment.END, false, true);
        runStackedLayoutTest(DualControlLayoutAlignment.END, true, true);
    }

    /** Runs a test where the controls don't fit on the same line. */
    private void runStackedLayoutTest(int alignment, boolean isRtl, boolean addPadding) {
        DualControlLayout layout = new DualControlLayout(mContext, null);
        if (addPadding) layout.setPadding(PADDING_LEFT, PADDING_TOP, PADDING_RIGHT, PADDING_BOTTOM);
        layout.setAlignment(alignment);
        layout.setStackedMargin(STACKED_MARGIN);
        layout.setLayoutDirection(isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
        layout.setLayoutParams(
                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));

        View primary = new Space(mContext);
        primary.setMinimumWidth(mTinyControlWidth);
        primary.setMinimumHeight(PRIMARY_HEIGHT);
        layout.addView(primary);

        View secondary = new Space(mContext);
        secondary.setMinimumWidth(INFOBAR_WIDTH);
        secondary.setMinimumHeight(SECONDARY_HEIGHT);
        layout.addView(secondary);

        // Trigger the measurement & layout algorithms.
        int parentWidthSpec = MeasureSpec.makeMeasureSpec(INFOBAR_WIDTH, MeasureSpec.AT_MOST);
        int parentHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        layout.measure(parentWidthSpec, parentHeightSpec);
        layout.layout(0, 0, layout.getMeasuredWidth(), layout.getMeasuredHeight());

        Assert.assertEquals(addPadding ? PADDING_LEFT : 0, primary.getLeft());
        Assert.assertEquals(addPadding ? PADDING_LEFT : 0, secondary.getLeft());

        Assert.assertEquals(INFOBAR_WIDTH - (addPadding ? PADDING_RIGHT : 0), primary.getRight());
        Assert.assertEquals(INFOBAR_WIDTH - (addPadding ? PADDING_RIGHT : 0), secondary.getRight());

        int expectedControlWidth = INFOBAR_WIDTH - (addPadding ? PADDING_LEFT + PADDING_RIGHT : 0);
        Assert.assertEquals(expectedControlWidth, primary.getMeasuredWidth());
        Assert.assertEquals(expectedControlWidth, secondary.getMeasuredWidth());
        Assert.assertEquals(INFOBAR_WIDTH, layout.getMeasuredWidth());

        int expectedPrimaryTop = addPadding ? PADDING_TOP : 0;
        int expectedPrimaryBottom = expectedPrimaryTop + primary.getHeight();
        int expectedSecondaryTop = expectedPrimaryBottom + STACKED_MARGIN;
        int expectedSecondaryBottom = expectedSecondaryTop + secondary.getHeight();
        int expectedLayoutHeight = expectedSecondaryBottom + (addPadding ? PADDING_BOTTOM : 0);
        Assert.assertEquals(expectedPrimaryTop, primary.getTop());
        Assert.assertEquals(expectedPrimaryBottom, primary.getBottom());
        Assert.assertEquals(expectedSecondaryTop, secondary.getTop());
        Assert.assertEquals(expectedSecondaryBottom, secondary.getBottom());
        Assert.assertEquals(expectedLayoutHeight, layout.getMeasuredHeight());
        Assert.assertNotEquals(layout.getMeasuredHeight(), primary.getMeasuredHeight());
    }

    /**
     * Runs a test against an inflated DualControlLayout that sets all of its values. Re-uses the
     * AutofillEditor's buttons XML layout because we have no support for test-only layout files.
     */
    @Test
    @SmallTest
    @UiThreadTest
    public void testInflation() {
        // Check that the basic DualControlLayout has nothing going on.
        DualControlLayout layout = new DualControlLayout(mContext, null);
        Assert.assertEquals(DualControlLayoutAlignment.START, layout.getAlignment());
        Assert.assertEquals(0, layout.getStackedMargin());
        Assert.assertNull(layout.findViewById(R.id.button_primary));
        Assert.assertNull(layout.findViewById(R.id.button_secondary));

        float dpToPx = mContext.getResources().getDisplayMetrics().density;
        // Inflate a DualControlLayout that has all of the attributes set and confirm they're used
        // correctly.
        FrameLayout containerView = new FrameLayout(mContext);
        LayoutInflater.from(mContext)
                .inflate(R.layout.dual_control_test_layout, containerView, true);
        DualControlLayout inflatedLayout = containerView.findViewById(R.id.button_bar);
        Assert.assertEquals(DualControlLayoutAlignment.END, inflatedLayout.getAlignment());
        Assert.assertEquals(
                "Incorrect stacked margin. Should be 24dp",
                24 * dpToPx,
                inflatedLayout.getStackedMargin(),
                0.f);

        Button primaryButton = inflatedLayout.findViewById(R.id.button_primary);
        Assert.assertNotNull(primaryButton);
        Assert.assertEquals("Done", primaryButton.getText());

        Button secondaryButton = inflatedLayout.findViewById(R.id.button_secondary);
        Assert.assertNotNull(secondaryButton);
        Assert.assertEquals("Cancel", secondaryButton.getText());
    }

    /** Runs a test that checks whether the primary and secondary buttons can be replaced. */
    @Test
    @SmallTest
    @UiThreadTest
    public void testReplaceButtons() {
        // Inflate a DualControlLayout that has all of the attributes set and confirm they're used
        // correctly.
        FrameLayout containerView = new FrameLayout(mContext);
        LayoutInflater.from(mContext)
                .inflate(R.layout.dual_control_test_layout, containerView, true);
        DualControlLayout inflatedLayout = containerView.findViewById(R.id.button_bar);

        inflatedLayout.removeAllViews();

        Button primaryButton = inflatedLayout.findViewById(R.id.button_primary);
        Assert.assertNull(primaryButton);
        Button secondaryButton = inflatedLayout.findViewById(R.id.button_secondary);
        Assert.assertNull(secondaryButton);

        inflatedLayout.addView(
                DualControlLayout.createButtonForLayout(
                        mContext, ButtonType.PRIMARY_FILLED, "Done", null));
        inflatedLayout.addView(
                DualControlLayout.createButtonForLayout(
                        mContext, ButtonType.SECONDARY_TEXT, "Cancel", null));

        Button newPrimaryButton = inflatedLayout.findViewById(R.id.button_primary);
        Assert.assertNotNull(newPrimaryButton);
        Assert.assertEquals("Done", newPrimaryButton.getText());

        Button newSecondaryButton = inflatedLayout.findViewById(R.id.button_secondary);
        Assert.assertNotNull(newSecondaryButton);
        Assert.assertEquals("Cancel", newSecondaryButton.getText());
    }
}