chromium/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinatorTest.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.creator;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;

import androidx.test.ext.junit.rules.ActivityScenarioRule;

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.chromium.base.supplier.UnownedUserDataSupplier;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.creator.CreatorCoordinator.ContentChangedListener;
import org.chromium.chrome.browser.creator.test.R;
import org.chromium.chrome.browser.feed.FeedActionDelegate;
import org.chromium.chrome.browser.feed.FeedListContentManager.ExternalViewContent;
import org.chromium.chrome.browser.feed.FeedListContentManager.FeedContent;
import org.chromium.chrome.browser.feed.FeedListContentManager.NativeViewContent;
import org.chromium.chrome.browser.feed.FeedReliabilityLoggingBridge;
import org.chromium.chrome.browser.feed.FeedServiceBridge;
import org.chromium.chrome.browser.feed.FeedServiceBridgeJni;
import org.chromium.chrome.browser.feed.FeedStream;
import org.chromium.chrome.browser.feed.FeedSurfaceRendererBridge;
import org.chromium.chrome.browser.feed.FeedSurfaceRendererBridgeJni;
import org.chromium.chrome.browser.feed.SingleWebFeedEntryPoint;
import org.chromium.chrome.browser.feed.webfeed.WebFeedBridge;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.share.ShareDelegate;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.ui.base.TestActivity;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.widget.ButtonCompat;
import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;

import java.util.ArrayList;
import java.util.List;

/** Tests for {@link CreatorCoordinator}. */
@RunWith(BaseRobolectricTestRunner.class)
public class CreatorCoordinatorTest {
    @Mock private WebFeedBridge.Natives mWebFeedBridgeJniMock;
    @Mock private FeedServiceBridge.Natives mFeedServiceBridgeJniMock;
    @Mock private FeedSurfaceRendererBridge.Natives mFeedSurfaceRendererBridgeJniMock;
    @Mock private FeedReliabilityLoggingBridge.Natives mFeedReliabilityLoggingBridgeJniMock;
    @Mock private WindowAndroid mWindowAndroid;
    @Mock private SnackbarManager mSnackbarManager;
    @Mock private Profile mProfile;
    @Mock private WebContentsCreator mCreatorWebContents;
    @Mock private NewTabCreator mCreatorOpenTab;
    @Mock private UnownedUserDataSupplier<ShareDelegate> mShareDelegateSupplier;
    @Mock private FeedStream mStreamMock;
    @Mock private SignInInterstitialInitiator mSignInInterstitialInitiator;
    @Mock private FeedActionDelegate mFeedActionDelegate;

    @Rule
    public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
            new ActivityScenarioRule<>(TestActivity.class);

    @Rule public JniMocker mJniMocker = new JniMocker();

    private static final GURL DEFAULT_URL = JUnitTestGURLs.EXAMPLE_URL;
    private static final GURL TEST_URL = JUnitTestGURLs.URL_1;

    private final byte[] mWebFeedIdDefault = "webFeedId".getBytes();
    private final boolean mFollowingDefault = false;
    private final int mEntryPointDefault = SingleWebFeedEntryPoint.OTHER;
    private TestActivity mActivity;

    @Before
    public void setUpTest() {
        MockitoAnnotations.initMocks(this);
        mJniMocker.mock(FeedServiceBridgeJni.TEST_HOOKS, mFeedServiceBridgeJniMock);
        mJniMocker.mock(FeedSurfaceRendererBridgeJni.TEST_HOOKS, mFeedSurfaceRendererBridgeJniMock);
        mJniMocker.mock(WebFeedBridge.getTestHooksForTesting(), mWebFeedBridgeJniMock);
        mJniMocker.mock(
                FeedReliabilityLoggingBridge.getTestHooksForTesting(),
                mFeedReliabilityLoggingBridgeJniMock);

        mActivityScenarioRule.getScenario().onActivity(activity -> mActivity = activity);
    }

    private CreatorCoordinator newCreatorCoordinator(
            GURL url, byte[] webFeedId, int entryPoint, boolean following) {
        return new CreatorCoordinator(
                mActivity,
                webFeedId,
                mSnackbarManager,
                mWindowAndroid,
                mProfile,
                url == null ? null : url.getSpec(),
                mCreatorWebContents,
                mCreatorOpenTab,
                mShareDelegateSupplier,
                entryPoint,
                following,
                mSignInInterstitialInitiator);
    }

    @Test
    public void testCreatorCoordinatorConstruction() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        assertNotNull("Could not construct CreatorCoordinator", creatorCoordinator);
    }

    @Test
    public void testActionBar() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        View outerView = creatorCoordinator.getView();
        ViewGroup actionBar = (ViewGroup) outerView.findViewById(R.id.action_bar);
        assertNotNull("Could not retrieve ActionBar", actionBar);
    }

    @Test
    public void testOnChangeListener_noError() {
        CreatorCoordinator coordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        coordinator.setStreamForTest(mStreamMock);
        ContentChangedListener listener = coordinator.new ContentChangedListener();

        List<FeedContent> contents = new ArrayList<>();
        contents.add(new NativeViewContent(0, "header", new View(mActivity)));
        contents.add(new ExternalViewContent("content", new byte[0], null));

        listener.onContentChanged(contents);

        verify(mStreamMock).removeOnContentChangedListener(listener);
        verify(mStreamMock).notifyNewHeaderCount(2);
    }

    @Test
    public void testOnChangeListener_error() {
        CreatorCoordinator coordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        coordinator.setStreamForTest(mStreamMock);
        ContentChangedListener listener = coordinator.new ContentChangedListener();

        List<FeedContent> contents = new ArrayList<>();
        contents.add(new NativeViewContent(0, "header", new View(mActivity)));
        contents.add(new NativeViewContent(0, "error", new View(mActivity)));

        // Set the title to avoid deleting the profile section.
        coordinator.getCreatorModel().set(CreatorProperties.TITLE_KEY, "creatorTitle");

        listener.onContentChanged(contents);

        verify(mStreamMock, never()).notifyNewHeaderCount(anyInt());
        verify(mStreamMock, never()).removeOnContentChangedListener(any());
    }

    @Test
    public void testOnChangeListener_RemoveHeader() {
        CreatorCoordinator coordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        coordinator.setStreamForTest(mStreamMock);
        ContentChangedListener listener = coordinator.new ContentChangedListener();

        List<FeedContent> contents = new ArrayList<>();
        contents.add(new NativeViewContent(0, "header", new View(mActivity)));
        contents.add(new NativeViewContent(0, "error", new View(mActivity)));

        // Make sure title is unavailable to test deletion of profile section.
        coordinator.getCreatorModel().set(CreatorProperties.TITLE_KEY, null);

        listener.onContentChanged(contents);

        verify(mStreamMock).notifyNewHeaderCount(0);
        verify(mStreamMock).removeOnContentChangedListener(listener);
    }

    @Test
    public void testCreatorModel_Creation() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        PropertyModel creatorModel = creatorCoordinator.getCreatorModel();
        assertNotNull("Could not retrieve CreatorModel", creatorModel);
    }

    @Test
    public void testCreatorModel_DefaultFollowing() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, true);
        PropertyModel creatorModel = creatorCoordinator.getCreatorModel();
        assertTrue(creatorModel.get(CreatorProperties.IS_FOLLOWED_KEY));

        View creatorProfileView = creatorCoordinator.getProfileView();
        ButtonCompat followButton = creatorProfileView.findViewById(R.id.creator_follow_button);
        ButtonCompat followingButton =
                creatorProfileView.findViewById(R.id.creator_following_button);

        assertEquals(followButton.getVisibility(), View.GONE);
        assertEquals(followingButton.getVisibility(), View.VISIBLE);
    }

    @Test
    public void testCreatorModel_DefaultUrl() {
        GURL creatorUrl = TEST_URL;
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        creatorUrl, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        PropertyModel creatorModel = creatorCoordinator.getCreatorModel();
        String modelUrl = creatorModel.get(CreatorProperties.URL_KEY);
        assertEquals(creatorUrl.getSpec(), modelUrl);

        View creatorProfileView = creatorCoordinator.getProfileView();
        TextView urlView = creatorProfileView.findViewById(R.id.creator_url);
        assertEquals(
                UrlFormatter.formatUrlForDisplayOmitSchemePathAndTrivialSubdomains(creatorUrl),
                urlView.getText());
    }

    @Test
    public void testCreatorModel_DefaultWebFeedId() {
        byte[] creatorWebFeedId = "creatorWebFeedId".getBytes();
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, creatorWebFeedId, mEntryPointDefault, mFollowingDefault);
        PropertyModel creatorModel = creatorCoordinator.getCreatorModel();
        byte[] modelWebFeedId = creatorModel.get(CreatorProperties.WEB_FEED_ID_KEY);
        assertEquals(creatorWebFeedId, modelWebFeedId);
    }

    @Test
    public void testCreatorModel_NewTitle() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        String newTitle = "creatorTitle 2.0";
        PropertyModel creatorModel = creatorCoordinator.getCreatorModel();
        creatorModel.set(CreatorProperties.TITLE_KEY, newTitle);
        String title = creatorModel.get(CreatorProperties.TITLE_KEY);
        assertEquals(newTitle, title);

        View creatorProfileView = creatorCoordinator.getProfileView();
        TextView profileTitleView = creatorProfileView.findViewById(R.id.creator_name);
        assertEquals(newTitle, profileTitleView.getText());

        View creatorView = creatorCoordinator.getView();
        TextView toolbarTitleView = creatorView.findViewById(R.id.creator_title_toolbar);
        assertEquals(newTitle, toolbarTitleView.getText());
    }

    @Test
    public void testCreatorModel_NewUrl() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        String newUrl = TEST_URL.getSpec();
        PropertyModel creatorModel = creatorCoordinator.getCreatorModel();
        creatorModel.set(CreatorProperties.URL_KEY, newUrl);
        String url = creatorModel.get(CreatorProperties.URL_KEY);
        assertEquals(newUrl, url);

        View creatorProfileView = creatorCoordinator.getProfileView();
        TextView urlView = creatorProfileView.findViewById(R.id.creator_url);
        assertEquals(
                UrlFormatter.formatUrlForDisplayOmitSchemePathAndTrivialSubdomains(DEFAULT_URL),
                urlView.getText());
    }

    @Test
    public void testCreatorModel_ToolbarVisibility() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        PropertyModel creatorModel = creatorCoordinator.getCreatorModel();
        View creatorView = creatorCoordinator.getView();
        FrameLayout mButtonsContainer = creatorView.findViewById(R.id.creator_all_buttons_toolbar);
        assertEquals(mButtonsContainer.getVisibility(), View.GONE);

        creatorModel.set(CreatorProperties.IS_TOOLBAR_VISIBLE_KEY, true);
        assertEquals(mButtonsContainer.getVisibility(), View.VISIBLE);
    }

    @Test
    public void testCreatorModel_IsFollowedStatus() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        PropertyModel creatorModel = creatorCoordinator.getCreatorModel();
        View creatorProfileView = creatorCoordinator.getProfileView();
        ButtonCompat followButton = creatorProfileView.findViewById(R.id.creator_follow_button);
        ButtonCompat followingButton =
                creatorProfileView.findViewById(R.id.creator_following_button);

        creatorModel.set(CreatorProperties.IS_FOLLOWED_KEY, false);
        assertEquals(followButton.getVisibility(), View.VISIBLE);
        assertEquals(followingButton.getVisibility(), View.GONE);

        creatorModel.set(CreatorProperties.IS_FOLLOWED_KEY, true);
        assertEquals(followButton.getVisibility(), View.GONE);
        assertEquals(followingButton.getVisibility(), View.VISIBLE);
    }

    @Test
    public void testCreatorCoordinator_QueryFeed_nullUrl() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        null, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        PropertyModel creatorModel = creatorCoordinator.getCreatorModel();
        creatorCoordinator.queryFeedStream(mFeedActionDelegate, mShareDelegateSupplier);
        verify(mWebFeedBridgeJniMock).queryWebFeedId(anyString(), any());
    }

    @Test
    public void testCreatorCoordinator_QueryFeed_nullWebFeedId() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(DEFAULT_URL, null, mEntryPointDefault, mFollowingDefault);
        PropertyModel creatorModel = creatorCoordinator.getCreatorModel();
        creatorCoordinator.queryFeedStream(mFeedActionDelegate, mShareDelegateSupplier);
        verify(mWebFeedBridgeJniMock).queryWebFeed(anyString(), any());
    }

    @Test
    public void testCreatorCoordinator_InitializeBottomSheetView() {
        CreatorCoordinator creatorCoordinator =
                newCreatorCoordinator(
                        DEFAULT_URL, mWebFeedIdDefault, mEntryPointDefault, mFollowingDefault);
        ViewGroup creatorViewGroup = creatorCoordinator.getView();
        assertEquals(creatorViewGroup.getChildCount(), 2);
        View contentPreviewsBottomSheet =
                creatorViewGroup.findViewById(R.id.creator_content_preview_bottom_sheet);
        assertNotNull(
                "Content Previews Bottom Sheet is not initialized", contentPreviewsBottomSheet);
    }
}