blob: 81a5436636831bdfb5e89cbd8a221674419856a7 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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.night_mode;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.chromium.base.ApplicationState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ApplicationStatus.ApplicationStateListener;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.test.ShadowRecordHistogram;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.night_mode.WebContentsDarkModeController.AutoDarkSettingsChangeSource;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.browser_ui.site_settings.SingleCategorySettings.AutoDarkSiteSettingObserver;
import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge;
import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridgeJni;
import org.chromium.components.content_settings.ContentSettingValues;
import org.chromium.components.content_settings.ContentSettingsType;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.components.user_prefs.UserPrefsJni;
import org.chromium.url.GURL;
/** Unit tests for {@link WebContentsDarkModeController}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE,
shadows = {WebContentsDarkModeControllerUnitTest.ShadowApplicationStatus.class,
ShadowRecordHistogram.class})
public class WebContentsDarkModeControllerUnitTest {
@Implements(ApplicationStatus.class)
static class ShadowApplicationStatus {
@ApplicationState
static int sApplicationState;
@Nullable
static ApplicationStateListener sLastListener;
@Implementation
public static int getStateForApplication() {
return sApplicationState;
}
@Implementation
public static void registerApplicationStateListener(ApplicationStateListener listener) {
sLastListener = listener;
}
}
static class TestNightModeStateProvider implements NightModeStateProvider {
public boolean mIsInNightMode;
public @Nullable Observer mObserver;
@Override
public boolean isInNightMode() {
return mIsInNightMode;
}
@Override
public void addObserver(@NonNull Observer observer) {
mObserver = observer;
}
@Override
public void removeObserver(@NonNull Observer observer) {
if (observer == mObserver) mObserver = null;
}
}
@Rule
public JniMocker mJniMocker = new JniMocker();
@Mock
UserPrefs.Natives mMockUserPrefJni;
@Mock
WebsitePreferenceBridge.Natives mMockWebsitePreferenceBridgeJni;
@Mock
PrefService mMockPrefService;
@Mock
Profile mMockProfile;
@Mock
GURL mMockGurl;
TestNightModeStateProvider mNightModeStateProvider;
WebContentsDarkModeController mWebContentsDarkModeController;
boolean mIsGlobalSettingsEnabled;
@ContentSettingValues
int mIsAutoDarkEnabledForUrlContentSettingValue;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mJniMocker.mock(UserPrefsJni.TEST_HOOKS, mMockUserPrefJni);
mJniMocker.mock(WebsitePreferenceBridgeJni.TEST_HOOKS, mMockWebsitePreferenceBridgeJni);
Mockito.when(mMockUserPrefJni.get(any())).thenReturn(mMockPrefService);
mNightModeStateProvider = new TestNightModeStateProvider();
GlobalNightModeStateProviderHolder.setInstanceForTesting(mNightModeStateProvider);
Profile.setLastUsedProfileForTesting(mMockProfile);
ShadowApplicationStatus.sApplicationState = ApplicationState.HAS_RUNNING_ACTIVITIES;
ShadowRecordHistogram.reset();
Mockito.doAnswer(invocation -> {
mIsGlobalSettingsEnabled = (boolean) invocation.getArguments()[2];
return null;
})
.when(mMockWebsitePreferenceBridgeJni)
.setContentSettingEnabled(eq(mMockProfile),
eq(ContentSettingsType.AUTO_DARK_WEB_CONTENT), anyBoolean());
Mockito.doAnswer(invocation -> mIsGlobalSettingsEnabled)
.when(mMockWebsitePreferenceBridgeJni)
.isContentSettingEnabled(
eq(mMockProfile), eq(ContentSettingsType.AUTO_DARK_WEB_CONTENT));
Mockito.doAnswer(invocation -> {
mIsAutoDarkEnabledForUrlContentSettingValue = (int) invocation.getArguments()[4];
return null;
})
.when(mMockWebsitePreferenceBridgeJni)
.setContentSettingDefaultScope(eq(mMockProfile),
eq(ContentSettingsType.AUTO_DARK_WEB_CONTENT), notNull(), notNull(),
anyInt());
Mockito.doAnswer(invocation -> mIsAutoDarkEnabledForUrlContentSettingValue)
.when(mMockWebsitePreferenceBridgeJni)
.getContentSetting(eq(mMockProfile), eq(ContentSettingsType.AUTO_DARK_WEB_CONTENT),
notNull(), notNull());
}
@After
public void tearDown() {
GlobalNightModeStateProviderHolder.setInstanceForTesting(null);
WebContentsDarkModeController.setTestInstance(null);
Profile.setLastUsedProfileForTesting(null);
ShadowApplicationStatus.sApplicationState = ApplicationState.UNKNOWN;
ShadowApplicationStatus.sLastListener = null;
ShadowRecordHistogram.reset();
}
@Test
public void testApplicationStatus_RunningActivities() {
mWebContentsDarkModeController = WebContentsDarkModeController.createInstance();
assertIsObservingNightMode(true);
Assert.assertEquals("Controller should be started and observing application status.",
mWebContentsDarkModeController, ShadowApplicationStatus.sLastListener);
ShadowApplicationStatus.sApplicationState = ApplicationState.HAS_STOPPED_ACTIVITIES;
ShadowApplicationStatus.sLastListener.onApplicationStateChange(
ShadowApplicationStatus.sApplicationState);
assertIsObservingNightMode(false);
}
@Test
public void testApplicationStatus_NoRunningActivities() {
ShadowApplicationStatus.sApplicationState = ApplicationState.HAS_STOPPED_ACTIVITIES;
mWebContentsDarkModeController = WebContentsDarkModeController.createInstance();
Assert.assertEquals("Controller should be started and observing application status.",
mWebContentsDarkModeController, ShadowApplicationStatus.sLastListener);
assertIsObservingNightMode(false);
}
private void doTestSetAutoDarkGlobalSettingsEnabled(boolean enabled) {
mNightModeStateProvider.mIsInNightMode = true;
WebContentsDarkModeController.setGlobalUserSettings(enabled);
Assert.assertEquals(
"Auto dark settings state incorrect.", enabled, mIsGlobalSettingsEnabled);
assertForceDarkModeEnabled(enabled);
assertAutoDarkModeChangeSourceRecorded(
AutoDarkSettingsChangeSource.THEME_SETTINGS, enabled, 1);
}
@Test
public void testGlobalSettingsEnabled() {
doTestSetAutoDarkGlobalSettingsEnabled(true);
}
@Test
public void testGlobalSettingsDisabled() {
doTestSetAutoDarkGlobalSettingsEnabled(false);
}
private void doTestSetAutoDarkForUrl(boolean enableForUrl) {
mNightModeStateProvider.mIsInNightMode = true;
Mockito.doReturn(ContentSettingValues.ALLOW)
.when(mMockWebsitePreferenceBridgeJni)
.getDefaultContentSetting(
eq(mMockProfile), eq(ContentSettingsType.AUTO_DARK_WEB_CONTENT));
WebContentsDarkModeController.setEnabledForUrl(mMockProfile, mMockGurl, enableForUrl);
Assert.assertEquals("Auto dark for URL is incorrect.", enableForUrl,
WebContentsDarkModeController.isEnabledForUrl(mMockProfile, mMockGurl));
assertAutoDarkModeChangeSourceRecorded(
AutoDarkSettingsChangeSource.APP_MENU, enableForUrl, 1);
}
@Test
public void testEnableForUrl_Enabled() {
doTestSetAutoDarkForUrl(true);
}
@Test
public void testEnableForUrl_Disabled() {
doTestSetAutoDarkForUrl(false);
}
@Test
public void testAutoDarkSiteSettingsObserver_DefaultValueChanged() {
AutoDarkSiteSettingObserver observer = WebContentsDarkModeController.createInstance();
observer.onDefaultValueChanged(true);
assertAutoDarkModeChangeSourceRecorded(
AutoDarkSettingsChangeSource.SITE_SETTINGS_GLOBAL, true, 1);
observer.onDefaultValueChanged(false);
assertAutoDarkModeChangeSourceRecorded(
AutoDarkSettingsChangeSource.SITE_SETTINGS_GLOBAL, false, 1);
}
@Test
public void testAutoDarkSiteSettingsObserver_SiteExceptionChanged() {
AutoDarkSiteSettingObserver observer = WebContentsDarkModeController.createInstance();
observer.onSiteExceptionChanged(true);
assertAutoDarkModeChangeSourceRecorded(
AutoDarkSettingsChangeSource.SITE_SETTINGS_EXCEPTION_LIST, false, 1);
observer.onSiteExceptionChanged(false);
assertAutoDarkModeChangeSourceRecorded(
AutoDarkSettingsChangeSource.SITE_SETTINGS_EXCEPTION_LIST, true, 1);
}
@Test
public void testOnNightModeChange() {
mIsGlobalSettingsEnabled = true;
mNightModeStateProvider.mIsInNightMode = false;
mWebContentsDarkModeController = WebContentsDarkModeController.createInstance();
assertIsObservingNightMode(true);
assertForceDarkModeEnabled(false);
mNightModeStateProvider.mIsInNightMode = true;
mNightModeStateProvider.mObserver.onNightModeStateChanged();
assertForceDarkModeEnabled(true);
}
private void assertForceDarkModeEnabled(boolean enabled) {
Mockito.verify(mMockPrefService)
.setBoolean(eq(Pref.WEB_KIT_FORCE_DARK_MODE_ENABLED), eq(enabled));
}
private void assertIsObservingNightMode(boolean isObserving) {
if (isObserving) {
Assert.assertNotNull("Controller should be started and observing night mode.",
mNightModeStateProvider.mObserver);
} else {
Assert.assertNull("Controller will not should not observing night mode.",
mNightModeStateProvider.mObserver);
}
}
private void assertAutoDarkModeChangeSourceRecorded(
@AutoDarkSettingsChangeSource int source, boolean enabled, int expectedCounts) {
String histogramName = "Android.DarkTheme.AutoDarkMode.SettingsChangeSource."
+ (enabled ? "Enabled" : "Disabled");
int actualCount = RecordHistogram.getHistogramValueCountForTesting(histogramName, source);
Assert.assertEquals("Histogram <" + histogramName + "> for sample <" + source
+ "> is not recorded correctly.",
expectedCounts, actualCount);
}
}