// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import type {AppearanceBrowserProxy, /*CrButtonElement,*/ CustomizeColorSchemeModeClientRemote, SettingsAppearancePageElement, SettingsDropdownMenuElement} from 'chrome://settings/settings.js';
import {AppearanceBrowserProxyImpl, ColorSchemeMode, CustomizeColorSchemeModeBrowserProxy, CustomizeColorSchemeModeClientCallbackRouter, CustomizeColorSchemeModeHandlerRemote, SystemTheme} from 'chrome://settings/settings.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
import {TestMock} from 'chrome://webui-test/test_mock.js';
import {isVisible, microtasksFinished} from 'chrome://webui-test/test_util.js';
class TestAppearanceBrowserProxy extends TestBrowserProxy implements
AppearanceBrowserProxy {
private defaultZoom_: number = 1;
private isChildAccount_: boolean = false;
private isHomeUrlValid_: boolean = true;
private pinnedToolbarActionsAreDefaultResponse_: boolean = true;
constructor() {
super([
'getDefaultZoom',
'getThemeInfo',
'isChildAccount',
'openCustomizeChrome',
'openCustomizeChromeToolbarSection',
'recordHoverCardImagesEnabledChanged',
'resetPinnedToolbarActions',
'useDefaultTheme',
// <if expr="is_linux">
'useGtkTheme',
'useQtTheme',
// </if>
'validateStartupPage',
'pinnedToolbarActionsAreDefault',
]);
}
getDefaultZoom() {
this.methodCalled('getDefaultZoom');
return Promise.resolve(this.defaultZoom_);
}
getThemeInfo(themeId: string) {
this.methodCalled('getThemeInfo', themeId);
return Promise.resolve({
id: '',
name: 'Sports car red',
shortName: '',
description: '',
version: '',
mayDisable: false,
enabled: false,
isApp: false,
offlineEnabled: false,
optionsUrl: '',
permissions: [],
hostPermissions: [],
});
}
isChildAccount() {
this.methodCalled('isChildAccount');
return this.isChildAccount_;
}
openCustomizeChrome() {
this.methodCalled('openCustomizeChrome');
}
openCustomizeChromeToolbarSection() {
this.methodCalled('openCustomizeChromeToolbarSection');
}
recordHoverCardImagesEnabledChanged(enabled: boolean) {
this.methodCalled('recordHoverCardImagesEnabledChanged', enabled);
}
resetPinnedToolbarActions() {
this.methodCalled('resetPinnedToolbarActions');
}
useDefaultTheme() {
this.methodCalled('useDefaultTheme');
}
// <if expr="is_linux">
useGtkTheme() {
this.methodCalled('useGtkTheme');
}
useQtTheme() {
this.methodCalled('useQtTheme');
}
// </if>
setDefaultZoom(defaultZoom: number) {
this.defaultZoom_ = defaultZoom;
}
setIsChildAccount(isChildAccount: boolean) {
this.isChildAccount_ = isChildAccount;
}
validateStartupPage(url: string) {
this.methodCalled('validateStartupPage', url);
return Promise.resolve(this.isHomeUrlValid_);
}
setValidStartupPageResponse(isValid: boolean) {
this.isHomeUrlValid_ = isValid;
}
pinnedToolbarActionsAreDefault() {
this.methodCalled('pinnedToolbarActionsAreDefault');
return Promise.resolve(this.pinnedToolbarActionsAreDefaultResponse_);
}
setPinnedToolbarActionsAreDefaultResponse(areDefault: boolean) {
this.pinnedToolbarActionsAreDefaultResponse_ = areDefault;
}
}
let appearancePage: SettingsAppearancePageElement;
let appearanceBrowserProxy: TestAppearanceBrowserProxy;
let colorSchemeHandler: TestMock<CustomizeColorSchemeModeHandlerRemote>&
CustomizeColorSchemeModeHandlerRemote;
let colorSchemeCallbackRouter: CustomizeColorSchemeModeClientRemote;
function createAppearancePage() {
appearanceBrowserProxy.reset();
document.body.innerHTML = window.trustedTypes!.emptyHTML;
colorSchemeHandler =
TestMock.fromClass(CustomizeColorSchemeModeHandlerRemote);
CustomizeColorSchemeModeBrowserProxy.setInstance(
colorSchemeHandler, new CustomizeColorSchemeModeClientCallbackRouter());
colorSchemeCallbackRouter = CustomizeColorSchemeModeBrowserProxy.getInstance()
.callbackRouter.$.bindNewPipeAndPassRemote();
appearancePage = document.createElement('settings-appearance-page');
appearancePage.set('prefs', {
autogenerated: {
theme: {
policy: {
color: {
type: chrome.settingsPrivate.PrefType.NUMBER,
value: 0,
},
},
},
},
browser: {
show_forward_button: {
type: chrome.settingsPrivate.PrefType.BOOLEAN,
value: true,
},
show_home_button: {
type: chrome.settingsPrivate.PrefType.BOOLEAN,
value: false,
},
},
extensions: {
theme: {
id: {
type: chrome.settingsPrivate.PrefType.STRING,
value: '',
},
system_theme: {
type: chrome.settingsPrivate.PrefType.NUMBER,
value: SystemTheme.DEFAULT,
},
},
},
tab_search: {
is_right_aligned: {
type: chrome.settingsPrivate.PrefType.BOOLEAN,
value: false,
},
},
});
appearancePage.set('pageVisibility', {
setWallpaper: true,
});
document.body.appendChild(appearancePage);
flush();
}
suite('AppearanceHandler', function() {
setup(function() {
appearanceBrowserProxy = new TestAppearanceBrowserProxy();
AppearanceBrowserProxyImpl.setInstance(appearanceBrowserProxy);
createAppearancePage();
});
teardown(function() {
appearancePage.remove();
});
const THEME_ID_PREF = 'prefs.extensions.theme.id.value';
// <if expr="is_linux">
const SYSTEM_THEME_PREF = 'prefs.extensions.theme.system_theme.value';
test('useDefaultThemeLinux', async () => {
await colorSchemeHandler.whenCalled('initializeColorSchemeMode');
assertFalse(!!appearancePage.get(THEME_ID_PREF));
assertEquals(appearancePage.get(SYSTEM_THEME_PREF), SystemTheme.DEFAULT);
// No custom nor system theme in use; "USE CLASSIC" should be hidden.
assertFalse(!!appearancePage.shadowRoot!.querySelector('#useDefault'));
// The color scheme toggle should be visible when the classic theme is used.
assertTrue(isVisible(appearancePage.$.colorSchemeModeRow));
appearancePage.set(SYSTEM_THEME_PREF, SystemTheme.GTK);
flush();
// If the system theme is in use, "USE CLASSIC" should show.
assertTrue(!!appearancePage.shadowRoot!.querySelector('#useDefault'));
// The color scheme toggle should be hidden when the GTK theme is used.
assertFalse(isVisible(appearancePage.$.colorSchemeModeRow));
appearancePage.set(SYSTEM_THEME_PREF, SystemTheme.DEFAULT);
appearancePage.set(THEME_ID_PREF, 'fake theme id');
flush();
// With a custom theme installed, "USE CLASSIC" should show.
const button =
appearancePage.shadowRoot!.querySelector<HTMLElement>('#useDefault');
assertTrue(!!button);
button.click();
return appearanceBrowserProxy.whenCalled('useDefaultTheme');
});
test('useGtkThemeLinux', async () => {
await colorSchemeHandler.whenCalled('initializeColorSchemeMode');
assertFalse(!!appearancePage.get(THEME_ID_PREF));
appearancePage.set(SYSTEM_THEME_PREF, SystemTheme.GTK);
flush();
// The "USE GTK+" button shouldn't be showing if it's already in use.
assertFalse(!!appearancePage.shadowRoot!.querySelector('#useGtk'));
// The color scheme toggle should be hidden when the GTK theme is used.
assertFalse(isVisible(appearancePage.$.colorSchemeModeRow));
appearanceBrowserProxy.setIsChildAccount(true);
appearancePage.set(SYSTEM_THEME_PREF, SystemTheme.DEFAULT);
flush();
// Child account users have their own theme and can't use GTK+ theme.
assertFalse(!!appearancePage.shadowRoot!.querySelector('#useDefault'));
assertFalse(!!appearancePage.shadowRoot!.querySelector('#useGtk'));
// If there's no "USE" buttons, the container should be hidden.
assertTrue(
appearancePage.shadowRoot!
.querySelector<HTMLElement>('#themesSecondaryActions')!.hidden);
// The color scheme toggle should be visible when the classic theme is used,
// for child accounts.
assertTrue(isVisible(appearancePage.$.colorSchemeModeRow));
appearanceBrowserProxy.setIsChildAccount(false);
appearancePage.set(THEME_ID_PREF, 'fake theme id');
flush();
// If there's "USE" buttons again, the container should be visible.
assertTrue(!!appearancePage.shadowRoot!.querySelector('#useDefault'));
assertFalse(
appearancePage.shadowRoot!
.querySelector<HTMLElement>('#themesSecondaryActions')!.hidden);
// The color scheme toggle should be visible when a custom theme is used.
assertTrue(isVisible(appearancePage.$.colorSchemeModeRow));
const button =
appearancePage.shadowRoot!.querySelector<HTMLElement>('#useGtk');
assertTrue(!!button);
button.click();
return appearanceBrowserProxy.whenCalled('useGtkTheme');
});
// </if>
// <if expr="not is_linux">
test('useDefaultTheme', function() {
assertFalse(!!appearancePage.get(THEME_ID_PREF));
assertFalse(!!appearancePage.shadowRoot!.querySelector('#useDefault'));
appearancePage.set(THEME_ID_PREF, 'fake theme id');
flush();
// With a custom theme installed, "RESET TO DEFAULT" should show.
const button =
appearancePage.shadowRoot!.querySelector<HTMLElement>('#useDefault');
assertTrue(!!button);
button.click();
return appearanceBrowserProxy.whenCalled('useDefaultTheme');
});
test('useDefaultThemeWithPolicy', function() {
const POLICY_THEME_COLOR_PREF = 'prefs.autogenerated.theme.policy.color';
assertFalse(!!appearancePage.shadowRoot!.querySelector('#useDefault'));
// "Reset to default" button doesn't appear as result of a policy theme.
appearancePage.set(POLICY_THEME_COLOR_PREF, {controlledBy: 'PRIMARY_USER'});
flush();
assertFalse(!!appearancePage.shadowRoot!.querySelector('#useDefault'));
// Unset policy theme and set custom theme to get button to show.
appearancePage.set(POLICY_THEME_COLOR_PREF, {});
appearancePage.set(THEME_ID_PREF, 'fake theme id');
flush();
let button =
appearancePage.shadowRoot!.querySelector<HTMLElement>('#useDefault');
assertTrue(!!button);
// Clicking "Reset to default" button when a policy theme is applied
// causes the managed theme dialog to appear.
appearancePage.set(POLICY_THEME_COLOR_PREF, {controlledBy: 'PRIMARY_USER'});
flush();
button =
appearancePage.shadowRoot!.querySelector<HTMLElement>('#useDefault');
assertTrue(!!button);
assertEquals(
null, appearancePage.shadowRoot!.querySelector('managed-dialog'));
button.click();
flush();
assertFalse(
appearancePage.shadowRoot!.querySelector('managed-dialog')!.hidden);
});
// </if>
test('openCustomizeChrome', function() {
loadTimeData.overrideValues({
toolbarPinningEnabled: true,
});
createAppearancePage();
const button =
appearancePage.shadowRoot!.querySelector<HTMLElement>('#openTheme');
assertTrue(!!button);
button.click();
return appearanceBrowserProxy.whenCalled('openCustomizeChrome');
});
test('openCustomizeChromeToolbarSection', function() {
loadTimeData.overrideValues({
toolbarPinningEnabled: true,
});
createAppearancePage();
const button = appearancePage.shadowRoot!.querySelector<HTMLElement>(
'#customizeToolbar');
assertTrue(!!button);
button.click();
return appearanceBrowserProxy.whenCalled(
'openCustomizeChromeToolbarSection');
});
test('resetPinnedToolbarActions', async function() {
loadTimeData.overrideValues({
toolbarPinningEnabled: true,
});
appearanceBrowserProxy.setPinnedToolbarActionsAreDefaultResponse(false);
createAppearancePage();
await microtasksFinished();
const button = appearancePage.shadowRoot!.querySelector<HTMLElement>(
'#resetPinnedToolbarActions');
assertTrue(!!button);
button.click();
return appearanceBrowserProxy.whenCalled('resetPinnedToolbarActions');
});
test('resetHiddenWhenNoPinnedActions', async function() {
loadTimeData.overrideValues({
toolbarPinningEnabled: true,
});
appearanceBrowserProxy.setPinnedToolbarActionsAreDefaultResponse(true);
createAppearancePage();
await microtasksFinished();
const button = appearancePage.shadowRoot!.querySelector<HTMLElement>(
'#resetPinnedToolbarActions');
assertFalse(!!button);
});
test('ColorSchemeMode', async () => {
assertFalse(isVisible(appearancePage.$.colorSchemeModeRow));
colorSchemeHandler.reset();
createAppearancePage();
await colorSchemeHandler.whenCalled('initializeColorSchemeMode');
assertTrue(isVisible(appearancePage.$.colorSchemeModeRow));
assertEquals(
1, colorSchemeHandler.getCallCount('initializeColorSchemeMode'));
// Assert that changes to the color scheme mode updates the select menu.
colorSchemeCallbackRouter.setColorSchemeMode(ColorSchemeMode.kLight);
assertEquals(
`${ColorSchemeMode.kLight}`,
appearancePage.$.colorSchemeModeSelect.value);
// Assert that changing the select menu updates the color scheme.
appearancePage.$.colorSchemeModeSelect.value = `${ColorSchemeMode.kDark}`;
appearancePage.$.colorSchemeModeSelect.dispatchEvent(new Event('change'));
const handlerArg =
await colorSchemeHandler.whenCalled('setColorSchemeMode');
assertEquals(ColorSchemeMode.kDark, handlerArg);
});
test('default zoom handling', async function() {
function getDefaultZoomText() {
const zoomLevel = appearancePage.$.zoomLevel;
return zoomLevel.options[zoomLevel.selectedIndex]!.textContent!.trim();
}
await appearanceBrowserProxy.whenCalled('getDefaultZoom');
assertEquals('100%', getDefaultZoomText());
appearanceBrowserProxy.setDefaultZoom(2 / 3);
createAppearancePage();
await appearanceBrowserProxy.whenCalled('getDefaultZoom');
assertEquals('67%', getDefaultZoomText());
appearanceBrowserProxy.setDefaultZoom(11 / 10);
createAppearancePage();
await appearanceBrowserProxy.whenCalled('getDefaultZoom');
assertEquals('110%', getDefaultZoomText());
appearanceBrowserProxy.setDefaultZoom(1.7499999999999);
createAppearancePage();
await appearanceBrowserProxy.whenCalled('getDefaultZoom');
assertEquals('175%', getDefaultZoomText());
});
test('show home button toggling', function() {
assertFalse(
!!appearancePage.shadowRoot!.querySelector('#home-button-options'));
appearancePage.set('prefs', {
autogenerated: {theme: {policy: {color: {value: 0}}}},
browser: {show_home_button: {value: true}},
extensions: {theme: {id: {value: ''}}},
toolbar: {pinned_actions: {value: []}},
});
flush();
assertTrue(
!!appearancePage.shadowRoot!.querySelector('#home-button-options'));
});
test('show side panel options', function() {
createAppearancePage();
assertTrue(
!!appearancePage.shadowRoot!.querySelector('#sidePanelPosition'));
});
test('show tab search options', async function() {
loadTimeData.overrideValues({
showTabSearchPositionSettings: true,
});
createAppearancePage();
await microtasksFinished();
assertTrue(
!!appearancePage.shadowRoot!.querySelector('#tabSearchPositionRow'));
});
test('hide tab search options', async function() {
loadTimeData.overrideValues({
showTabSearchPositionSettings: false,
});
createAppearancePage();
await microtasksFinished();
assertTrue(
!appearancePage.shadowRoot!.querySelector('#tabSearchPositionRow'));
});
test('ShowSavedTabGroupsToggleVisible', async function() {
loadTimeData.overrideValues({
tabGroupsSaveUIUpdateEnabled: true,
});
createAppearancePage();
await microtasksFinished();
assertTrue(isVisible(appearancePage.$.showSavedTabGroups));
});
test('ShowSavedTabGroupsToggleHidden', async function() {
loadTimeData.overrideValues({
tabGroupsSaveUIUpdateEnabled: false,
});
createAppearancePage();
await microtasksFinished();
assertFalse(isVisible(appearancePage.$.showSavedTabGroups));
});
test('ShowAutoPinNewTabGroupsToggleVisible', async function() {
loadTimeData.overrideValues({
tabGroupsSaveUIUpdateEnabled: true,
});
createAppearancePage();
await microtasksFinished();
assertTrue(isVisible(appearancePage.$.autoPinNewTabGroups));
});
test('ShowAutoPinNewTabGroupsToggleHidden', async function() {
loadTimeData.overrideValues({
tabGroupsSaveUIUpdateEnabled: false,
});
createAppearancePage();
await microtasksFinished();
assertFalse(isVisible(appearancePage.$.autoPinNewTabGroups));
});
});
suite('TabSearchPositionSettings', () => {
const TAB_SEARCH_IS_RIGHT_ALIGNED_PREF_PATH = 'tab_search.is_right_aligned';
const DEFAULT_TAB_SEARCH_IS_RIGHT_ALIGNED = false;
const UI_FEATURE_ALIGN_LEFT = 'foo';
const UI_FEATURE_ALIGN_RIGHT = 'bar';
const FALSEY_STRING = 'false';
const TRUTHY_STRING = 'true';
async function buildPage(startupPref: boolean, currentPref: boolean) {
loadTimeData.overrideValues({
uiFeatureAlignLeft: UI_FEATURE_ALIGN_LEFT,
uiFeatureAlignRight: UI_FEATURE_ALIGN_RIGHT,
showTabSearchPositionSettings: true,
tabSearchIsRightAlignedAtStartup: startupPref,
});
createAppearancePage();
appearancePage.setPrefValue(
TAB_SEARCH_IS_RIGHT_ALIGNED_PREF_PATH, currentPref);
flush();
await microtasksFinished();
}
function getTabSearchDropdown(): SettingsDropdownMenuElement|null {
return appearancePage.shadowRoot!
.querySelector<SettingsDropdownMenuElement>(
'#tabSearchPositionDropdown');
}
function getTabSearchRestartButton(): HTMLElement|null {
return appearancePage.shadowRoot!.querySelector(
'#tabSearchPositionRestart');
}
async function userClicksDropdownForOption(userChoice: boolean) {
const dropdown: SettingsDropdownMenuElement|null = getTabSearchDropdown();
if (dropdown === null) {
return;
}
dropdown.$.dropdownMenu.value = userChoice ? TRUTHY_STRING : FALSEY_STRING;
dropdown.dispatchEvent(new CustomEvent('change'));
// simulate the pref changing in the backend, This doesnt get triggered
// because the prefs are hardcoded.
appearancePage.setPrefValue(
TAB_SEARCH_IS_RIGHT_ALIGNED_PREF_PATH, userChoice);
flush();
await microtasksFinished();
}
setup(async () => {
await buildPage(
DEFAULT_TAB_SEARCH_IS_RIGHT_ALIGNED,
DEFAULT_TAB_SEARCH_IS_RIGHT_ALIGNED);
});
test('shows when showTabSearchPositionSettings is true', () => {
assertTrue(!!getTabSearchDropdown());
});
test('dropdown has expected options', () => {
const dropdown: SettingsDropdownMenuElement|null = getTabSearchDropdown();
assertTrue(!!dropdown);
assertEquals(2, dropdown?.menuOptions.length);
assertTrue(!!dropdown?.menuOptions.some(
option => option.name === UI_FEATURE_ALIGN_LEFT &&
option.value === FALSEY_STRING));
assertTrue(!!dropdown?.menuOptions.some(
option => option.name === UI_FEATURE_ALIGN_RIGHT &&
option.value === TRUTHY_STRING));
});
test('dropdown sets the value', async () => {
const dropdown: SettingsDropdownMenuElement|null = getTabSearchDropdown();
// Should be set to initial option of "False" based on pref.
assertEquals(FALSEY_STRING, dropdown?.getSelectedValue());
// on user click of true, the dropdown should now show truthy
await userClicksDropdownForOption(/*userChoice=*/ true);
assertEquals(TRUTHY_STRING, dropdown?.getSelectedValue());
// on user click of false, the dropdown should now show falsey
await userClicksDropdownForOption(/*userChoice=*/ false);
assertEquals(FALSEY_STRING, dropdown?.getSelectedValue());
});
test('restart button A11y', async () => {
await buildPage(/*startupPref=*/ false, /*currentPref=*/ true);
const button = getTabSearchRestartButton();
assertTrue(!!button);
// The restart button needs to have the "alert" aria attribute.
assertEquals('alert', button.role);
});
test('restart button steady state', async () => {
await buildPage(/*startupPref=*/ false, /*currentPref=*/ false);
assertFalse(!!getTabSearchRestartButton());
await buildPage(/*startupPref=*/ false, /*currentPref=*/ true);
assertTrue(!!getTabSearchRestartButton());
await buildPage(/*startupPref=*/ true, /*currentPref=*/ false);
assertTrue(!!getTabSearchRestartButton());
await buildPage(/*startupPref=*/ true, /*currentPref=*/ true);
assertFalse(!!getTabSearchRestartButton());
});
test('restart button shows on change', async () => {
assertFalse(!!getTabSearchRestartButton());
await userClicksDropdownForOption(/*userChoice=*/ true);
assertTrue(!!getTabSearchRestartButton());
await userClicksDropdownForOption(/*userChoice=*/ false);
assertFalse(!!getTabSearchRestartButton());
});
});