chromium/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorViewBinderTest.java

// Copyright 2019 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.status_indicator;

import static android.graphics.PorterDuff.Mode.SRC_IN;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import android.graphics.Color;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;

import androidx.core.content.res.ResourcesCompat;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;

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

import org.chromium.base.MathUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.chrome.R;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.browser_ui.widget.ViewResourceFrameLayout;
import org.chromium.components.browser_ui.widget.text.TextViewWithCompoundDrawables;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.test.util.BlankUiTestActivityTestCase;

/** Tests for {@link StatusIndicatorViewBinder}. */
@RunWith(ChromeJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
public class StatusIndicatorViewBinderTest extends BlankUiTestActivityTestCase {
    private static final String STATUS_TEXT = "Offline";

    private ViewResourceFrameLayout mContainer;
    private TextViewWithCompoundDrawables mStatusTextView;
    private MockStatusIndicatorSceneLayer mSceneLayer;

    private PropertyModel mModel;
    private PropertyModelChangeProcessor mMCP;

    @Override
    public void setUpTest() throws Exception {
        super.setUpTest();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getActivity().setContentView(R.layout.status_indicator_container);
                    mContainer = getActivity().findViewById(R.id.status_indicator);
                    mStatusTextView = mContainer.findViewById(R.id.status_text);

                    mSceneLayer = new MockStatusIndicatorSceneLayer();
                    mModel =
                            new PropertyModel.Builder(StatusIndicatorProperties.ALL_KEYS)
                                    .with(StatusIndicatorProperties.STATUS_TEXT, "")
                                    .with(StatusIndicatorProperties.STATUS_ICON, null)
                                    .with(
                                            StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY,
                                            View.GONE)
                                    .with(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false)
                                    .build();
                    mMCP =
                            PropertyModelChangeProcessor.create(
                                    mModel,
                                    new StatusIndicatorViewBinder.ViewHolder(
                                            mContainer, mSceneLayer),
                                    StatusIndicatorViewBinder::bind);
                });
    }

    @Override
    public void tearDownTest() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(mMCP::destroy);
        super.tearDownTest();
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testTextView() {
        assertTrue("Wrong initial status text.", TextUtils.isEmpty(mStatusTextView.getText()));
        assertNull("Wrong initial status icon.", mStatusTextView.getCompoundDrawablesRelative()[0]);
        assertTrue(
                "Rest of the compound drawables are not null.", areRestOfCompoundDrawablesNull());

        Drawable drawable =
                ResourcesCompat.getDrawable(
                        getActivity().getResources(),
                        R.drawable.ic_error_white_24dp_filled,
                        getActivity().getTheme());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mModel.set(StatusIndicatorProperties.STATUS_TEXT, STATUS_TEXT);
                    mModel.set(StatusIndicatorProperties.STATUS_ICON, drawable);
                });

        assertEquals("Wrong status text.", STATUS_TEXT, mStatusTextView.getText());
        assertEquals(
                "Wrong status icon.", drawable, mStatusTextView.getCompoundDrawablesRelative()[0]);
        assertTrue(
                "Rest of the compound drawables are not null.", areRestOfCompoundDrawablesNull());
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testVisibility() {
        assertEquals(
                "Wrong initial Android view visibility.", View.GONE, mContainer.getVisibility());
        assertFalse(
                "Wrong initial composited view visibility.",
                mSceneLayer.isSceneOverlayTreeShowing());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mModel.set(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY, View.VISIBLE);
                    mModel.set(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, true);
                });

        assertEquals("Android view is not visible.", View.VISIBLE, mContainer.getVisibility());
        assertTrue("Composited view is not visible.", mSceneLayer.isSceneOverlayTreeShowing());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mModel.set(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY, View.GONE);
                    mModel.set(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false);
                });

        assertEquals("Android view is not gone.", View.GONE, mContainer.getVisibility());
        assertFalse("Composited view is visible.", mSceneLayer.isSceneOverlayTreeShowing());
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testColorAndTint() {
        int bgColor = SemanticColorUtils.getDefaultBgColor(getActivity());
        int textColor = SemanticColorUtils.getDefaultTextColor(getActivity());
        assertEquals(
                "Wrong initial background color.",
                bgColor,
                ((ColorDrawable) mContainer.getBackground()).getColor());
        assertEquals("Wrong initial text color", textColor, mStatusTextView.getCurrentTextColor());

        Drawable drawable =
                ResourcesCompat.getDrawable(
                        getActivity().getResources(),
                        R.drawable.ic_error_white_24dp_filled,
                        getActivity().getTheme());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mModel.set(StatusIndicatorProperties.STATUS_ICON, drawable);
                    mModel.set(StatusIndicatorProperties.BACKGROUND_COLOR, Color.BLUE);
                    mModel.set(StatusIndicatorProperties.TEXT_COLOR, Color.RED);
                    mModel.set(StatusIndicatorProperties.ICON_TINT, Color.GREEN);
                });

        assertEquals(
                "Wrong background color.",
                Color.BLUE,
                ((ColorDrawable) mContainer.getBackground()).getColor());
        assertEquals("Wrong text color.", Color.RED, mStatusTextView.getCurrentTextColor());
        assertEquals(
                "Wrong compound drawables tint",
                new PorterDuffColorFilter(Color.GREEN, SRC_IN),
                mStatusTextView.getCompoundDrawablesRelative()[0].getColorFilter());
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testTextAlpha() {
        assertEquals(
                "Wrong initial text alpha.", 1.f, mStatusTextView.getAlpha(), MathUtils.EPSILON);

        ThreadUtils.runOnUiThreadBlocking(
                () -> mModel.set(StatusIndicatorProperties.TEXT_ALPHA, .5f));

        assertEquals("Wrong text alpha.", .5f, mStatusTextView.getAlpha(), MathUtils.EPSILON);

        ThreadUtils.runOnUiThreadBlocking(
                () -> mModel.set(StatusIndicatorProperties.TEXT_ALPHA, .0f));

        assertEquals("Wrong text alpha.", 0.f, mStatusTextView.getAlpha(), MathUtils.EPSILON);

        ThreadUtils.runOnUiThreadBlocking(
                () -> mModel.set(StatusIndicatorProperties.TEXT_ALPHA, 1.f));

        assertEquals("Wrong text alpha.", 1.f, mStatusTextView.getAlpha(), MathUtils.EPSILON);
    }

    private boolean areRestOfCompoundDrawablesNull() {
        final Drawable[] drawables = mStatusTextView.getCompoundDrawablesRelative();
        for (int i = 1; i < drawables.length; i++) {
            if (drawables[i] != null) {
                return false;
            }
        }

        return true;
    }

    /** Mock {@link StatusIndicatorSceneLayer} class to avoid native initialization. */
    private class MockStatusIndicatorSceneLayer extends StatusIndicatorSceneLayer {
        MockStatusIndicatorSceneLayer() {
            super(null);
        }

        @Override
        protected void initializeNative() {}

        @Override
        public void destroy() {}
    }
}