chromium/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoMediatorUnitTest.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.logo;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static org.chromium.chrome.browser.preferences.ChromePreferenceKeys.APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO;

import android.content.Context;

import androidx.test.core.app.ApplicationProvider;

import org.junit.Assert;
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.MockitoAnnotations;
import org.robolectric.annotation.Config;

import org.chromium.base.Callback;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.homepage.HomepageManager;
import org.chromium.chrome.browser.logo.LogoBridge.Logo;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.components.image_fetcher.ImageFetcher;
import org.chromium.components.search_engines.TemplateUrl;
import org.chromium.components.search_engines.TemplateUrlService;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.modelutil.PropertyModel;

/** Unit tests for the {@link LogoMediator}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class LogoMediatorUnitTest {
    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private Profile mProfile;

    @Mock LogoBridge.Natives mLogoBridgeJniMock;

    @Mock LogoBridge mLogoBridge;

    @Mock ImageFetcher mImageFetcher;

    @Mock TemplateUrlService mTemplateUrlService;

    @Mock TemplateUrl mTemplateUrl;

    @Mock Callback<LoadUrlParams> mLogoClickedCallback;

    @Mock Callback<Logo> mOnLogoAvailableCallback;

    @Captor
    private ArgumentCaptor<TemplateUrlService.TemplateUrlServiceObserver>
            mTemplateUrlServiceObserverArgumentCaptor;

    private Context mContext;
    private PropertyModel mLogoModel;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mContext = ApplicationProvider.getApplicationContext();

        TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService);
        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(true);
        when(mTemplateUrlService.doesDefaultSearchEngineHaveLogo()).thenReturn(true);
        when(mTemplateUrlService.getDefaultSearchEngineTemplateUrl()).thenReturn(mTemplateUrl);
        when(mTemplateUrl.getKeyword()).thenReturn(null);

        mJniMocker.mock(LogoBridgeJni.TEST_HOOKS, mLogoBridgeJniMock);

        ThreadUtils.runOnUiThreadBlocking(
                () -> HomepageManager.getInstance().setPrefHomepageEnabled(true));

        mLogoModel = new PropertyModel(LogoProperties.ALL_KEYS);
    }

    @Test
    public void testDseChangedAndGoogleIsDseAndDoodleIsSupported() {
        LogoMediator logoMediator = createMediator();
        Assert.assertNotNull(logoMediator.getDefaultGoogleLogo(mContext));

        verify(mTemplateUrlService)
                .addObserver(mTemplateUrlServiceObserverArgumentCaptor.capture());
        mTemplateUrlServiceObserverArgumentCaptor.getValue().onTemplateURLServiceChanged();

        verify(mLogoBridge, times(1)).getCurrentLogo(any());
    }

    @Test
    public void testDseChangedAndGoogleIsDseAndDoodleIsNotSupported() {
        // If doodle isn't supported, getSearchProviderLogo() shouldn't be called by
        // onTemplateURLServiceChanged().
        LogoMediator logoMediator = createMediator(false);
        Assert.assertNotNull(logoMediator.getDefaultGoogleLogo(mContext));

        verify(mTemplateUrlService)
                .addObserver(mTemplateUrlServiceObserverArgumentCaptor.capture());
        mTemplateUrlServiceObserverArgumentCaptor.getValue().onTemplateURLServiceChanged();

        verify(mLogoBridge, times(0)).getCurrentLogo(any());
    }

    @Test
    public void testDseChangedAndGoogleIsNotDse() {
        LogoMediator logoMediator = createMediator();
        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(false);
        Assert.assertNull(logoMediator.getDefaultGoogleLogo(mContext));

        verify(mTemplateUrlService)
                .addObserver(mTemplateUrlServiceObserverArgumentCaptor.capture());
        mTemplateUrlServiceObserverArgumentCaptor.getValue().onTemplateURLServiceChanged();

        verify(mLogoBridge, times(1)).getCurrentLogo(any());
    }

    @Test
    public void testDseChangedAndDoesNotHaveLogo() {
        createMediator();
        when(mTemplateUrlService.doesDefaultSearchEngineHaveLogo()).thenReturn(false);

        verify(mTemplateUrlService)
                .addObserver(mTemplateUrlServiceObserverArgumentCaptor.capture());
        mTemplateUrlServiceObserverArgumentCaptor.getValue().onTemplateURLServiceChanged();

        verify(mLogoBridge, times(0)).getCurrentLogo(any());
    }

    @Test
    public void testLoadLogoWhenLogoNotLoaded() {
        LogoMediator logoMediator = createMediator();
        logoMediator.setHasLogoLoadedForCurrentSearchEngineForTesting(false);
        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(false);

        logoMediator.updateVisibility(/* animationEnabled= */ false);

        verify(mLogoBridge, times(1)).getCurrentLogo(any());
    }

    @Test
    public void testLoadLogoWhenLogoHasLoaded() {
        LogoMediator logoMediator = createMediator();
        logoMediator.setHasLogoLoadedForCurrentSearchEngineForTesting(true);
        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(false);

        logoMediator.updateVisibility(/* animationEnabled= */ false);

        verify(mLogoBridge, times(0)).getCurrentLogo(any());
    }

    @Test
    public void testInitWithNativeWhenParentSurfaceIsVisible() {
        LogoMediator logoMediator = createMediatorWithoutNative(true);
        logoMediator.updateVisibility(/* animationEnabled= */ false);

        Assert.assertTrue(logoMediator.isLogoVisible());
        // When parent surface is shown while native library isn't loaded, calling
        // updateVisibilityAndMaybeCleanUp() will add a pending load task.
        Assert.assertTrue(logoMediator.getIsLoadPendingForTesting());
        logoMediator.initWithNative(mProfile);

        Assert.assertTrue(logoMediator.isLogoVisible());
        verify(mLogoBridge, times(1)).getCurrentLogo(any());
        verify(mTemplateUrlService).addObserver(logoMediator);
    }

    @Test
    public void testInitWithoutNativeWhenDseDoesNotHaveLogo() {
        LogoMediator logoMediator = createMediatorWithoutNative(true);
        boolean originKeyValue =
                ChromeSharedPreferences.getInstance()
                        .readBoolean(
                                APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO,
                                mTemplateUrlService.doesDefaultSearchEngineHaveLogo());
        ChromeSharedPreferences.getInstance()
                .writeBoolean(APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO, false);
        logoMediator.updateVisibility(/* animationEnabled= */ false);
        Assert.assertFalse(mLogoModel.get(LogoProperties.VISIBILITY));
        Assert.assertFalse(logoMediator.getIsLoadPendingForTesting());
        verify(mLogoBridge, times(0)).destroy();
        ChromeSharedPreferences.getInstance()
                .writeBoolean(APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO, originKeyValue);
    }

    @Test
    public void testUpdateVisibility() {
        LogoMediator logoMediator = createMediator();

        // If parent surface is shown, logo should be loaded and bridge shouldn't be
        // destroyed.
        logoMediator.setLogoBridgeForTesting(mLogoBridge);
        logoMediator.updateVisibility(/* animationEnabled= */ false);
        Assert.assertTrue(logoMediator.isLogoVisible());
        verify(mLogoBridge).getCurrentLogo(any());
        verify(mLogoBridge, never()).destroy();
        Assert.assertFalse(mLogoModel.get(LogoProperties.ANIMATION_ENABLED));

        // Attached the test for animationEnabled.
        logoMediator.setHasLogoLoadedForCurrentSearchEngineForTesting(false);
        logoMediator.updateVisibility(/* animationEnabled= */ true);
        Assert.assertTrue(mLogoModel.get(LogoProperties.ANIMATION_ENABLED));
    }

    @Test
    public void testDestroyWhenInitWithNative() {
        LogoMediator logoMediator = createMediator();
        logoMediator.setLogoBridgeForTesting(null);
        logoMediator.destroy();
        verify(mTemplateUrlService).removeObserver(logoMediator);
    }

    private LogoMediator createMediator(boolean shouldFetchDoodle) {
        LogoMediator logoMediator = createMediatorWithoutNative(shouldFetchDoodle);
        logoMediator.initWithNative(mProfile);
        return logoMediator;
    }

    private LogoMediator createMediator() {
        LogoMediator logoMediator = createMediatorWithoutNative(true);
        logoMediator.initWithNative(mProfile);
        return logoMediator;
    }

    private LogoMediator createMediatorWithoutNative(boolean shouldFetchDoodle) {
        LogoMediator logoMediator =
                new LogoMediator(
                        mContext,
                        mLogoClickedCallback,
                        mLogoModel,
                        shouldFetchDoodle,
                        mOnLogoAvailableCallback,
                        null,
                        new CachedTintedBitmap(
                                R.drawable.google_logo, R.color.google_logo_tint_color));
        logoMediator.setLogoBridgeForTesting(mLogoBridge);
        return logoMediator;
    }
}