chromium/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/mini/MiniPlayerCoordinatorUnitTest.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.chrome.browser.readaloud.player.mini;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewStub;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.browser_controls.BottomControlsStacker;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.layouts.LayoutManager;
import org.chromium.chrome.browser.readaloud.ReadAloudMiniPlayerSceneLayer;
import org.chromium.chrome.browser.readaloud.ReadAloudMiniPlayerSceneLayerJni;
import org.chromium.chrome.browser.readaloud.player.PlayerCoordinator;
import org.chromium.chrome.browser.readaloud.player.PlayerProperties;
import org.chromium.chrome.browser.readaloud.player.R;
import org.chromium.chrome.browser.readaloud.player.VisibilityState;
import org.chromium.chrome.browser.user_education.IPHCommand;
import org.chromium.chrome.browser.user_education.UserEducationHelper;
import org.chromium.chrome.modules.readaloud.PlaybackListener;
import org.chromium.ui.modelutil.PropertyModel;

/** Unit tests for {@link MiniPlayerCoordinator}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class MiniPlayerCoordinatorUnitTest {
    private static final String TITLE = "Title";
    private static final String PUBLISHER = "Publisher";

    @Rule public JniMocker mJniMocker = new JniMocker();
    @Mock ReadAloudMiniPlayerSceneLayer.Natives mSceneLayerNativeMock;

    @Mock private Activity mActivity;
    @Mock private Context mContextForInflation;
    @Mock private LayoutInflater mLayoutInflater;
    @Mock private ViewStub mViewStub;
    @Mock private BrowserControlsStateProvider mBrowserControlsStateProvider;
    @Mock private BottomControlsStacker mBottomControlsStacker;
    @Mock private LayoutManager mLayoutManager;
    @Mock private MiniPlayerLayout mLayout;
    @Mock private MiniPlayerMediator mMediator;
    @Mock private ReadAloudMiniPlayerSceneLayer mSceneLayer;
    @Mock private PlayerCoordinator mPlayerCoordinator;
    @Mock private UserEducationHelper mUserEducationHelper;
    @Mock private View mView;
    private PropertyModel mSharedModel;
    private PropertyModel mModel;

    private MiniPlayerCoordinator mCoordinator;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        doReturn(mLayout).when(mViewStub).inflate();
        doReturn(mViewStub).when(mActivity).findViewById(eq(R.id.readaloud_mini_player_stub));
        doReturn(mLayoutInflater)
                .when(mContextForInflation)
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mSharedModel = new PropertyModel.Builder(PlayerProperties.ALL_KEYS).build();
        mModel = new PropertyModel.Builder(Properties.ALL_KEYS).build();
        mJniMocker.mock(ReadAloudMiniPlayerSceneLayerJni.TEST_HOOKS, mSceneLayerNativeMock);
        doReturn(123456789L).when(mSceneLayerNativeMock).init(any());
        doReturn(mModel).when(mMediator).getModel();
        doReturn(mBrowserControlsStateProvider).when(mBottomControlsStacker).getBrowserControls();
        mCoordinator =
                new MiniPlayerCoordinator(
                        mContextForInflation,
                        mSharedModel,
                        mMediator,
                        mLayout,
                        mSceneLayer,
                        mLayoutManager,
                        mPlayerCoordinator,
                        mUserEducationHelper);
    }

    @Test
    public void testViewInflated() {
        // Test the real constructor
        reset(mViewStub);
        doReturn(mLayout).when(mViewStub).inflate();
        mCoordinator =
                new MiniPlayerCoordinator(
                        mActivity,
                        mContextForInflation,
                        mSharedModel,
                        mBottomControlsStacker,
                        mLayoutManager,
                        mPlayerCoordinator,
                        mUserEducationHelper);
        verify(mViewStub).inflate();
        verify(mLayoutManager).addSceneOverlay(eq(mSceneLayer));
    }

    @Test
    public void testShow() {
        mCoordinator.show(/* animate= */ false);
        verify(mMediator).show(eq(false));

        // Second show() shouldn't inflate the stub again.
        reset(mViewStub);
        mCoordinator.show(/* animate= */ false);
        verify(mMediator, times(2)).show(eq(false));
    }

    @Test
    public void testOnShown_requestingIPH() {
        // if there's no container to anchor IPH against, don't request it.
        mCoordinator.onShown(/*container*/ null);
        verify(mUserEducationHelper, never()).requestShowIPH(any(IPHCommand.class));

        mCoordinator.onShown(mView);
        verify(mUserEducationHelper).requestShowIPH(any(IPHCommand.class));
    }

    @Test
    public void testDismissWhenNeverShown() {
        // Ensure there's no crash.
        assertEquals(VisibilityState.GONE, mCoordinator.getVisibility());
        mCoordinator.dismiss(false);
    }

    @Test
    public void testDismiss() {
        mCoordinator.dismiss(/* animate= */ false);
        verify(mMediator).dismiss(eq(false));
    }

    @Test
    public void testBindPlaybackState() {
        mCoordinator.show(/* animate= */ true);
        mSharedModel.set(PlayerProperties.PLAYBACK_STATE, PlaybackListener.State.PLAYING);
        verify(mLayout).onPlaybackStateChanged(eq(PlaybackListener.State.PLAYING));
    }

    @Test
    public void testBindTitle() {
        mCoordinator.show(/* animate= */ true);
        mSharedModel.set(PlayerProperties.TITLE, TITLE);
        verify(mLayout).setTitle(eq(TITLE));
    }

    @Test
    public void testBindPublisher() {
        mCoordinator.show(/* animate= */ true);
        mSharedModel.set(PlayerProperties.PUBLISHER, PUBLISHER);
        verify(mLayout).setPublisher(eq(PUBLISHER));
    }

    @Test
    public void testBindProgress() {
        mCoordinator.show(/* animate= */ true);
        mSharedModel.set(PlayerProperties.PROGRESS, 0.5f);
        verify(mLayout).setProgress(eq(0.5f));
    }

    @Test
    public void testBindYOffset() {
        mCoordinator.show(/* animate= */ true);
        mModel.set(Properties.Y_OFFSET, -100);
        verify(mLayout).setYOffset(eq(-100));
    }
}