chromium/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedSnackbarControllerTest.java

// Copyright 2021 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.feed.webfeed;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.description;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.view.View;
import android.widget.TextView;

import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;

import org.chromium.base.LocaleUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.feed.FeedServiceBridge;
import org.chromium.chrome.browser.feed.FeedSurfaceTracker;
import org.chromium.chrome.browser.feed.StreamKind;
import org.chromium.chrome.browser.feed.test.R;
import org.chromium.chrome.browser.feed.v2.FeedUserActionType;
import org.chromium.chrome.browser.feed.webfeed.WebFeedSnackbarController.FeedLauncher;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.feature_engagement.TriggerDetails;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;

import java.util.Locale;

/** Tests {@link WebFeedSnackbarController}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(
        manifest = Config.NONE,
        shadows = {})
@DisableFeatures(ChromeFeatureList.FEED_FOLLOW_UI_UPDATE)
@SmallTest
public final class WebFeedSnackbarControllerTest {
    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Rule public JniMocker mJniMocker = new JniMocker();

    private static final GURL sTestUrl = JUnitTestGURLs.EXAMPLE_URL;
    private static final GURL sFaviconUrl = JUnitTestGURLs.RED_1;
    private static final String sTitle = "Example Title";
    private static final byte[] sFollowId = new byte[] {1, 2, 3};

    @Mock private FeedLauncher mFeedLauncher;
    @Mock private Tracker mTracker;
    @Mock public WebFeedBridge.Natives mWebFeedBridgeJniMock;
    @Mock public FeedServiceBridge.Natives mFeedServideBridgeJniMock;
    private Context mContext;
    @Mock private Profile mProfile;
    private ModalDialogManager mDialogManager =
            new ModalDialogManager(Mockito.mock(ModalDialogManager.Presenter.class), 0);
    @Mock private SnackbarManager mSnackbarManager;
    private WebFeedSnackbarController mWebFeedSnackbarController;
    @Mock private Tab mTab;
    // The current observer of Tab.
    private TabObserver mTabObserver;

    @Captor private ArgumentCaptor<Snackbar> mSnackbarCaptor;
    @Captor private ArgumentCaptor<WebFeedBridge.WebFeedPageInformation> mPageInformationCaptor;

    @Before
    public void setUp() {
        // Print logs to stdout.
        ShadowLog.stream = System.out;

        // Set default locale to other country in order not to make
        // FeedFeatures.isFeedFollowUiUpdateEnabled always return true.
        Configuration config = new Configuration();
        config.setLocale(new Locale("tl", "PH"));
        LocaleUtils.setDefaultLocalesFromConfiguration(config);

        ProfileManager.setLastUsedProfileForTesting(mProfile);
        MockitoAnnotations.initMocks(this);
        mJniMocker.mock(WebFeedBridge.getTestHooksForTesting(), mWebFeedBridgeJniMock);
        mJniMocker.mock(FeedServiceBridge.getTestHooksForTesting(), mFeedServideBridgeJniMock);
        mContext = Robolectric.setupActivity(Activity.class);
        when(mTracker.shouldTriggerHelpUI(FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(false);
        when(mTracker.shouldTriggerHelpUIWithSnooze(
                        FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(new TriggerDetails(false, false));
        when(mTab.getOriginalUrl()).thenReturn(sTestUrl);
        Mockito.doAnswer(
                        invocationOnMock -> {
                            mTabObserver = (TabObserver) invocationOnMock.getArgument(0);
                            return null;
                        })
                .when(mTab)
                .addObserver(any());
        Mockito.doAnswer(
                        invocationOnMock -> {
                            mTabObserver = null;
                            return null;
                        })
                .when(mTab)
                .removeObserver(any());
        TrackerFactory.setTrackerForTests(mTracker);

        mWebFeedSnackbarController =
                new WebFeedSnackbarController(
                        RuntimeEnvironment.application,
                        mFeedLauncher,
                        mDialogManager,
                        mSnackbarManager);
    }

    @Test
    public void showSnackbarForFollow_successful_withMetadata() {
        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                getSuccessfulFollowResult(),
                sFollowId,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for successful follow.",
                Snackbar.UMA_WEB_FEED_FOLLOW_SUCCESS,
                snackbar.getIdentifierForTesting());
        assertEquals(
                "Snackbar message should be for successful follow with title from metadata.",
                mContext.getString(
                        R.string.web_feed_follow_success_snackbar_message,
                        getSuccessfulFollowResult().metadata.title),
                snackbar.getTextForTesting());
    }

    @Test
    public void showPostSuccessfulFollowHelp_ShowsSnackbar_FromForYouFeed() {
        mWebFeedSnackbarController.showPostSuccessfulFollowHelp(
                sTitle, true, StreamKind.FOR_YOU, null, null);

        assertFalse("Dialog should not be showing.", mDialogManager.isShowing());
        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for successful follow.",
                Snackbar.UMA_WEB_FEED_FOLLOW_SUCCESS,
                snackbar.getIdentifierForTesting());
        assertEquals(
                "Snackbar message should be for successful follow with title from metadata.",
                mContext.getString(
                        R.string.web_feed_follow_success_snackbar_message,
                        getSuccessfulFollowResult().metadata.title),
                snackbar.getTextForTesting());
        assertEquals(
                "Snackbar action should be for going to the Following feed.",
                mContext.getString(
                        R.string.web_feed_follow_success_snackbar_action_go_to_following),
                snackbar.getActionText());
    }

    @Test
    public void showPostSuccessfulFollowHelp_ShowsSnackbar_FromFollowingFeed() {
        mWebFeedSnackbarController.showPostSuccessfulFollowHelp(
                sTitle, true, StreamKind.FOLLOWING, mTab, sTestUrl);

        assertFalse("Dialog should not be showing.", mDialogManager.isShowing());
        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for successful follow.",
                Snackbar.UMA_WEB_FEED_FOLLOW_SUCCESS,
                snackbar.getIdentifierForTesting());
        assertEquals(
                "Snackbar message should be for successful follow with title from metadata.",
                mContext.getString(
                        R.string.web_feed_follow_success_snackbar_message,
                        getSuccessfulFollowResult().metadata.title),
                snackbar.getTextForTesting());
        assertEquals(
                "Snackbar action should be for refreshing the contents of the feed.",
                mContext.getString(R.string.web_feed_follow_success_snackbar_action_refresh),
                snackbar.getActionText());
    }

    @Test
    public void showSnackbarForFollow_successful_noMetadata() {
        WebFeedBridge.FollowResults followResults =
                new WebFeedBridge.FollowResults(
                        WebFeedSubscriptionRequestStatus.SUCCESS, /* metadata= */ null);

        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                followResults,
                sFollowId,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for successful follow.",
                Snackbar.UMA_WEB_FEED_FOLLOW_SUCCESS,
                snackbar.getIdentifierForTesting());
        assertEquals(
                "Snackbar message should be for successful follow with title from input.",
                mContext.getString(R.string.web_feed_follow_success_snackbar_message, sTitle),
                snackbar.getTextForTesting());

        // Simulate a navigation, the snackbar is dismissed.
        mTabObserver.onPageLoadStarted(mTab, JUnitTestGURLs.URL_2);
        verify(mSnackbarManager).dismissSnackbars(eq(snackbar.getController()));
    }

    @Test
    public void showSnackbarForFollow_correctDuration() {
        WebFeedBridge.FollowResults followResults =
                new WebFeedBridge.FollowResults(
                        WebFeedSubscriptionRequestStatus.SUCCESS, /* metadata= */ null);

        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                followResults,
                sFollowId,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        assertEquals(
                "Snackbar duration for follow should be correct.",
                WebFeedSnackbarController.SNACKBAR_DURATION_MS,
                mSnackbarCaptor.getValue().getDuration());
    }

    @Test
    public void showPromoDialogForFollow_successful_active() {
        when(mTracker.shouldTriggerHelpUI(FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(true);
        when(mTracker.shouldTriggerHelpUIWithSnooze(
                        FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(new TriggerDetails(true, false));
        WebFeedBridge.FollowResults followResults = getSuccessfulFollowResult();

        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                followResults,
                sFollowId,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        View currentDialog =
                mDialogManager.getCurrentDialogForTest().get(ModalDialogProperties.CUSTOM_VIEW);
        verify(mSnackbarManager, times(0)).showSnackbar(any());
        assertTrue("Dialog should be showing.", mDialogManager.isShowing());
        assertEquals(
                "Dialog title should be correct for active follow.",
                mContext.getString(R.string.web_feed_post_follow_dialog_title, sTitle),
                ((TextView) currentDialog.findViewById(R.id.web_feed_dialog_title)).getText());
        assertEquals(
                "Dialog details should be for active follow.",
                mContext.getString(
                        R.string.web_feed_post_follow_dialog_stories_ready_description, sTitle),
                ((TextView) currentDialog.findViewById(R.id.web_feed_dialog_details)).getText());
    }

    @Test
    public void showPostSuccessfulFollowHelp_Dialog_FromForYouFeed() {
        when(mTracker.shouldTriggerHelpUI(FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(true);
        when(mTracker.shouldTriggerHelpUIWithSnooze(
                        FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(new TriggerDetails(true, false));

        mWebFeedSnackbarController.showPostSuccessfulFollowHelp(
                sTitle, true, StreamKind.FOR_YOU, mTab, sTestUrl);

        View currentDialog =
                mDialogManager.getCurrentDialogForTest().get(ModalDialogProperties.CUSTOM_VIEW);
        assertTrue("Dialog should be showing.", mDialogManager.isShowing());
        // TODO(b/243676323): figure out how to test the positive_button label, which is out of the
        // currentDialog hierarchy.

        // No snackbar should be shown after the dialog is closed.
        mDialogManager.dismissAllDialogs(DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
        verify(mSnackbarManager, times(0)).showSnackbar(any());
    }

    @Test
    public void showPostSuccessfulFollowHelp_DialogAndSnackbar_FromFollowingFeed() {
        when(mTracker.shouldTriggerHelpUI(FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(true);
        when(mTracker.shouldTriggerHelpUIWithSnooze(
                        FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(new TriggerDetails(true, false));

        mWebFeedSnackbarController.showPostSuccessfulFollowHelp(
                sTitle, true, StreamKind.FOLLOWING, mTab, sTestUrl);

        verify(mSnackbarManager, times(0)).showSnackbar(any());
        View currentDialog =
                mDialogManager.getCurrentDialogForTest().get(ModalDialogProperties.CUSTOM_VIEW);
        assertTrue("Dialog should be showing.", mDialogManager.isShowing());
        // TODO(b/243676323): figure out how to test the positive_button label, which is out of the
        // currentDialog hierarchy.

        // A snackbar should be shown when the dialog is closed, offering the refresh action.
        mDialogManager.dismissAllDialogs(DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for successful follow.",
                Snackbar.UMA_WEB_FEED_FOLLOW_SUCCESS,
                snackbar.getIdentifierForTesting());
        assertEquals(
                "Snackbar message should be for successful follow with title from metadata.",
                mContext.getString(
                        R.string.web_feed_follow_success_snackbar_message,
                        getSuccessfulFollowResult().metadata.title),
                snackbar.getTextForTesting());
        assertEquals(
                "Snackbar action should be for refreshing the contents of the feed.",
                mContext.getString(R.string.web_feed_follow_success_snackbar_action_refresh),
                snackbar.getActionText());
    }

    @Test
    public void showPromoDialogForFollow_successful_notActive() {
        when(mTracker.shouldTriggerHelpUI(FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(true);
        when(mTracker.shouldTriggerHelpUIWithSnooze(
                        FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(new TriggerDetails(true, false));
        WebFeedBridge.FollowResults followResults =
                new WebFeedBridge.FollowResults(
                        WebFeedSubscriptionRequestStatus.SUCCESS,
                        new WebFeedBridge.WebFeedMetadata(
                                sFollowId,
                                sTitle,
                                sTestUrl,
                                WebFeedSubscriptionRequestStatus.SUCCESS,
                                WebFeedAvailabilityStatus.INACTIVE,
                                /* isRecommended= */ false,
                                sFaviconUrl));

        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                followResults,
                sFollowId,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        View currentDialog =
                mDialogManager.getCurrentDialogForTest().get(ModalDialogProperties.CUSTOM_VIEW);
        verify(mSnackbarManager, times(0)).showSnackbar(any());
        assertTrue("Dialog should be showing.", mDialogManager.isShowing());
        assertEquals(
                "Dialog title should be correct for inactive follow.",
                mContext.getString(R.string.web_feed_post_follow_dialog_title, sTitle),
                ((TextView) currentDialog.findViewById(R.id.web_feed_dialog_title)).getText());
        assertEquals(
                "Dialog details should be for inactive follow.",
                mContext.getString(
                        R.string.web_feed_post_follow_dialog_stories_not_ready_description, sTitle),
                ((TextView) currentDialog.findViewById(R.id.web_feed_dialog_details)).getText());
    }

    @Test
    public void showPromoDialogForFollow_successful_noMetadata() {
        when(mTracker.shouldTriggerHelpUI(FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(true);
        when(mTracker.shouldTriggerHelpUIWithSnooze(
                        FeatureConstants.IPH_WEB_FEED_POST_FOLLOW_DIALOG_FEATURE))
                .thenReturn(new TriggerDetails(true, false));
        WebFeedBridge.FollowResults followResults =
                new WebFeedBridge.FollowResults(
                        WebFeedSubscriptionRequestStatus.SUCCESS, /* metadata= */ null);

        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                followResults,
                sFollowId,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        View currentDialog =
                mDialogManager.getCurrentDialogForTest().get(ModalDialogProperties.CUSTOM_VIEW);
        verify(mSnackbarManager, times(0)).showSnackbar(any());
        assertTrue("Dialog should be showing.", mDialogManager.isShowing());
        assertEquals(
                "Dialog title should be correct.",
                mContext.getString(R.string.web_feed_post_follow_dialog_title, sTitle),
                ((TextView) currentDialog.findViewById(R.id.web_feed_dialog_title)).getText());
        assertEquals(
                "Dialog details should be for active follow.",
                mContext.getString(
                        R.string.web_feed_post_follow_dialog_stories_not_ready_description, sTitle),
                ((TextView) currentDialog.findViewById(R.id.web_feed_dialog_details)).getText());
    }

    @Test
    public void showSnackbarForFollow_noId_unsuccessful() {
        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                failureFollowResults(),
                "".getBytes(),
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for unsuccessful follow.",
                Snackbar.UMA_WEB_FEED_FOLLOW_FAILURE,
                snackbar.getIdentifierForTesting());
        assertEquals(
                "Snackbar message should be for unsuccessful follow.",
                mContext.getString(R.string.web_feed_follow_generic_failure_snackbar_message),
                snackbar.getTextForTesting());
        assertEquals(
                mContext.getString(R.string.web_feed_generic_failure_snackbar_action),
                snackbar.getActionText());

        // Click follow try again button.
        snackbar.getController().onAction(null);
        verify(
                        mWebFeedBridgeJniMock,
                        description(
                                "FollowFromUrl should be called on follow try again when ID is not "
                                        + "available."))
                .followWebFeed(
                        mPageInformationCaptor.capture(),
                        eq(WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU),
                        any());
        assertEquals(sTestUrl, mPageInformationCaptor.getValue().mUrl);
        assertEquals(mTab, mPageInformationCaptor.getValue().mTab);
        verify(mFeedServideBridgeJniMock)
                .reportOtherUserAction(
                        StreamKind.UNKNOWN, FeedUserActionType.TAPPED_FOLLOW_TRY_AGAIN_ON_SNACKBAR);
    }

    @Test
    public void showSnackbarForFollow_tryAgain_dismissedAfterNavigation() {
        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                failureFollowResults(),
                "".getBytes(),
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for unsuccessful follow.",
                Snackbar.UMA_WEB_FEED_FOLLOW_FAILURE,
                snackbar.getIdentifierForTesting());
        assertEquals(
                "Snackbar message should be for unsuccessful follow.",
                mContext.getString(R.string.web_feed_follow_generic_failure_snackbar_message),
                snackbar.getTextForTesting());

        // Simulate a navigation, the snackbar is dismissed.
        mTabObserver.onPageLoadStarted(mTab, JUnitTestGURLs.URL_2);
        verify(mSnackbarManager).dismissSnackbars(eq(snackbar.getController()));
    }

    @Test
    public void showSnackbarForFollow_tryAgain_dismissedAfterTabHidden() {
        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                failureFollowResults(),
                "".getBytes(),
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for unsuccessful follow.",
                Snackbar.UMA_WEB_FEED_FOLLOW_FAILURE,
                snackbar.getIdentifierForTesting());
        assertEquals(
                "Snackbar message should be for unsuccessful follow.",
                mContext.getString(R.string.web_feed_follow_generic_failure_snackbar_message),
                snackbar.getTextForTesting());

        // Hide tab, the snackbar is dismissed.
        mTabObserver.onHidden(mTab, 0);
        verify(mSnackbarManager).dismissSnackbars(eq(snackbar.getController()));
    }

    @Test
    public void showSnackbarForFollow_followFailureAfterNavigation_showsNoTryAgainAction() {
        // mTab reports sTestUrl as the current page. Show a follow-failure snackbar for a different
        // page.
        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                failureFollowResults(),
                "".getBytes(),
                JUnitTestGURLs.URL_2,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());

        assertEquals(
                "Snackbar should have no action", null, mSnackbarCaptor.getValue().getActionText());
    }

    @Test
    public void showSnackbarForFollow_withId_unsuccessful() {
        WebFeedBridge.FollowResults followResults = failureFollowResults();

        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                followResults,
                sFollowId,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for unsuccessful follow.",
                Snackbar.UMA_WEB_FEED_FOLLOW_FAILURE,
                snackbar.getIdentifierForTesting());
        assertEquals(
                "Snackbar message should be for unsuccessful follow.",
                mContext.getString(R.string.web_feed_follow_generic_failure_snackbar_message),
                snackbar.getTextForTesting());

        // Click follow try again button.
        snackbar.getController().onAction(null);
        verify(
                        mWebFeedBridgeJniMock,
                        description(
                                "FollowFromId should be called on follow try again when ID is"
                                        + " available."))
                .followWebFeedById(
                        eq(sFollowId),
                        /* isDurable= */ eq(false),
                        eq(WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU),
                        any());
        verify(mFeedServideBridgeJniMock)
                .reportOtherUserAction(
                        StreamKind.UNKNOWN, FeedUserActionType.TAPPED_FOLLOW_TRY_AGAIN_ON_SNACKBAR);
    }

    @Test
    public void showSnackbarForUnfollow_successful() {
        mWebFeedSnackbarController.showSnackbarForUnfollow(
                WebFeedSubscriptionRequestStatus.SUCCESS,
                sFollowId,
                mTab,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for successful unfollow.",
                Snackbar.UMA_WEB_FEED_UNFOLLOW_SUCCESS,
                snackbar.getIdentifierForTesting());

        // Click refollow button.
        snackbar.getController().onAction(null);
        verify(mWebFeedBridgeJniMock, description("Follow should be called on refollow."))
                .followWebFeedById(
                        eq(sFollowId),
                        /* isDurable= */ eq(false),
                        eq(WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU),
                        any());
        verify(mFeedServideBridgeJniMock)
                .reportOtherUserAction(
                        StreamKind.UNKNOWN,
                        FeedUserActionType.TAPPED_REFOLLOW_AFTER_UNFOLLOW_ON_SNACKBAR);
    }

    @Test
    public void showSnackbarForUnfollow_successful_nourl() {
        mWebFeedSnackbarController.showSnackbarForUnfollow(
                WebFeedSubscriptionRequestStatus.SUCCESS,
                sFollowId,
                null,
                null,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for successful unfollow.",
                Snackbar.UMA_WEB_FEED_UNFOLLOW_SUCCESS,
                snackbar.getIdentifierForTesting());

        // Click refollow button.
        snackbar.getController().onAction(null);
        verify(mWebFeedBridgeJniMock, description("Follow should be called on refollow."))
                .followWebFeedById(
                        eq(sFollowId),
                        /* isDurable= */ eq(false),
                        eq(WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU),
                        any());
        verify(mFeedServideBridgeJniMock)
                .reportOtherUserAction(
                        StreamKind.UNKNOWN,
                        FeedUserActionType.TAPPED_REFOLLOW_AFTER_UNFOLLOW_ON_SNACKBAR);
    }

    @Test
    public void showSnackbarForUnfollow_unsuccessful() {
        mWebFeedSnackbarController.showSnackbarForUnfollow(
                WebFeedSubscriptionRequestStatus.FAILED_OFFLINE,
                sFollowId,
                mTab,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for unsuccessful unfollow.",
                Snackbar.UMA_WEB_FEED_UNFOLLOW_FAILURE,
                snackbar.getIdentifierForTesting());

        // Click unfollow try again button.
        snackbar.getController().onAction(null);
        verify(
                        mWebFeedBridgeJniMock,
                        description("Unfollow should be called on unfollow try again."))
                .unfollowWebFeed(
                        eq(sFollowId),
                        /* isDurable= */ eq(false),
                        eq(WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU),
                        any());
        verify(mFeedServideBridgeJniMock)
                .reportOtherUserAction(
                        StreamKind.UNKNOWN,
                        FeedUserActionType.TAPPED_UNFOLLOW_TRY_AGAIN_ON_SNACKBAR);
    }

    @Test
    public void showSnackbarForUnfollow_unsuccessful_nourl() {
        mWebFeedSnackbarController.showSnackbarForUnfollow(
                WebFeedSubscriptionRequestStatus.FAILED_OFFLINE,
                sFollowId,
                null,
                null,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar should be for unsuccessful unfollow.",
                Snackbar.UMA_WEB_FEED_UNFOLLOW_FAILURE,
                snackbar.getIdentifierForTesting());

        // Click unfollow try again button.
        snackbar.getController().onAction(null);
        verify(
                        mWebFeedBridgeJniMock,
                        description("Unfollow should be called on unfollow try again."))
                .unfollowWebFeed(
                        eq(sFollowId),
                        /* isDurable= */ eq(false),
                        eq(WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU),
                        any());
        verify(mFeedServideBridgeJniMock)
                .reportOtherUserAction(
                        StreamKind.UNKNOWN,
                        FeedUserActionType.TAPPED_UNFOLLOW_TRY_AGAIN_ON_SNACKBAR);
    }

    @Test
    public void showSnackbarForUnfollow_correctDuration() {
        mWebFeedSnackbarController.showSnackbarForUnfollow(
                WebFeedSubscriptionRequestStatus.SUCCESS,
                sFollowId,
                mTab,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);

        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();
        assertEquals(
                "Snackbar duration for unfollow should be correct.",
                WebFeedSnackbarController.SNACKBAR_DURATION_MS,
                snackbar.getDuration());
    }

    @Test
    public void postFollowSnackbarIsDismissedUponFeedSurfaceOpened() {
        mWebFeedSnackbarController.showPostFollowHelp(
                mTab,
                getSuccessfulFollowResult(),
                sFollowId,
                sTestUrl,
                sTitle,
                WebFeedBridge.CHANGE_REASON_WEB_PAGE_MENU);
        verify(mSnackbarManager).showSnackbar(mSnackbarCaptor.capture());
        Snackbar snackbar = mSnackbarCaptor.getValue();

        FeedSurfaceTracker.getInstance().surfaceOpened();
        verify(mSnackbarManager).dismissSnackbars(eq(mSnackbarCaptor.getValue().getController()));
    }

    private WebFeedBridge.FollowResults getSuccessfulFollowResult() {
        return new WebFeedBridge.FollowResults(
                WebFeedSubscriptionRequestStatus.SUCCESS,
                new WebFeedBridge.WebFeedMetadata(
                        sFollowId,
                        sTitle,
                        sTestUrl,
                        WebFeedSubscriptionStatus.SUBSCRIBED,
                        WebFeedAvailabilityStatus.ACTIVE,
                        /* isRecommended= */ true,
                        sFaviconUrl));
    }

    private WebFeedBridge.FollowResults failureFollowResults() {
        return new WebFeedBridge.FollowResults(
                WebFeedSubscriptionRequestStatus.FAILED_UNKNOWN_ERROR, null);
    }
}