blob: 53b465344dd38dab3546b221d875e70f07add1e9 [file] [log] [blame]
// Copyright 2017 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.
cr.define('languages_page_tests', function() {
/** @enum {string} */
const TestNames = {
AddLanguagesDialog: 'add languages dialog',
LanguageMenu: 'language menu',
InputMethods: 'input methods',
Spellcheck: 'spellcheck',
};
suite('languages page', function() {
/** @type {?LanguageHelper} */
let languageHelper = null;
/** @type {?SettingsLanguagesPageElement} */
let languagesPage = null;
/** @type {?IronCollapseElement} */
let languagesCollapse = null;
/** @type {?CrActionMenuElement} */
let actionMenu = null;
/** @type {?settings.LanguagesBrowserProxy} */
let browserProxy = null;
// Enabled language pref name for the platform.
const languagesPref = cr.isChromeOS ?
'settings.language.preferred_languages' :
'intl.accept_languages';
// Initial value of enabled languages pref used in tests.
const initialLanguages = 'en-US,sw';
suiteSetup(function() {
testing.Test.disableAnimationsAndTransitions();
PolymerTest.clearBody();
CrSettingsPrefs.deferInitialization = true;
});
setup(function() {
const settingsPrefs = document.createElement('settings-prefs');
const settingsPrivate =
new settings.FakeSettingsPrivate(settings.getFakeLanguagePrefs());
settingsPrefs.initialize(settingsPrivate);
document.body.appendChild(settingsPrefs);
return CrSettingsPrefs.initialized.then(function() {
// Set up test browser proxy.
browserProxy = new settings.TestLanguagesBrowserProxy();
settings.LanguagesBrowserProxyImpl.instance_ = browserProxy;
// Set up fake languageSettingsPrivate API.
const languageSettingsPrivate =
browserProxy.getLanguageSettingsPrivate();
languageSettingsPrivate.setSettingsPrefs(settingsPrefs);
languagesPage = document.createElement('settings-languages-page');
// Prefs would normally be data-bound to settings-languages-page.
languagesPage.prefs = settingsPrefs.prefs;
test_util.fakeDataBind(settingsPrefs, languagesPage, 'prefs');
document.body.appendChild(languagesPage);
languagesCollapse = languagesPage.$.languagesCollapse;
languagesCollapse.opened = true;
actionMenu = languagesPage.$.menu.get();
languageHelper = languagesPage.languageHelper;
return languageHelper.whenReady();
});
});
teardown(function() {
PolymerTest.clearBody();
});
suite(TestNames.AddLanguagesDialog, function() {
let dialog;
let dialogItems;
let cancelButton;
let actionButton;
let dialogClosedResolver;
let dialogClosedObserver;
// Resolves the PromiseResolver if the mutation includes removal of the
// settings-add-languages-dialog.
// TODO(michaelpg): Extract into a common method similar to
// test_util.whenAttributeIs for use elsewhere.
const onMutation = function(mutations, observer) {
if (mutations.some(function(mutation) {
return mutation.type == 'childList' &&
Array.from(mutation.removedNodes).includes(dialog);
})) {
// Sanity check: the dialog should no longer be in the DOM.
assertEquals(null, languagesPage.$$('settings-add-languages-dialog'));
observer.disconnect();
assertTrue(!!dialogClosedResolver);
dialogClosedResolver.resolve();
}
};
setup(function(done) {
const addLanguagesButton =
languagesCollapse.querySelector('#addLanguages');
const whenDialogOpen =
test_util.eventToPromise('cr-dialog-open', languagesPage);
addLanguagesButton.click();
// The page stamps the dialog, registers listeners, and populates the
// iron-list asynchronously at microtask timing, so wait for a new task.
whenDialogOpen.then(() => {
dialog = languagesPage.$$('settings-add-languages-dialog');
assertTrue(!!dialog);
// Observe the removal of the dialog via MutationObserver since the
// HTMLDialogElement 'close' event fires at an unpredictable time.
dialogClosedResolver = new PromiseResolver();
dialogClosedObserver = new MutationObserver(onMutation);
dialogClosedObserver.observe(languagesPage.root, {childList: true});
actionButton = assert(dialog.$$('.action-button'));
cancelButton = assert(dialog.$$('.cancel-button'));
Polymer.dom.flush();
// The fixed-height dialog's iron-list should stamp far fewer than
// 50 items.
dialogItems =
dialog.$.dialog.querySelectorAll('.list-item:not([hidden])');
assertGT(dialogItems.length, 1);
assertLT(dialogItems.length, 50);
// No languages have been checked, so the action button is disabled.
assertTrue(actionButton.disabled);
assertFalse(cancelButton.disabled);
done();
});
});
teardown(function() {
dialogClosedObserver.disconnect();
});
test('cancel', function() {
// Canceling the dialog should close and remove it.
cancelButton.click();
return dialogClosedResolver.promise;
});
test('add languages and cancel', function() {
// Check some languages.
dialogItems[1].click(); // en-CA.
dialogItems[2].click(); // tk.
// Canceling the dialog should close and remove it without enabling
// the checked languages.
cancelButton.click();
return dialogClosedResolver.promise.then(function() {
assertEquals(
initialLanguages, languageHelper.getPref(languagesPref).value);
});
});
test('add languages and confirm', function() {
// No languages have been checked, so the action button is inert.
actionButton.click();
Polymer.dom.flush();
assertEquals(dialog, languagesPage.$$('settings-add-languages-dialog'));
// Check and uncheck one language.
dialogItems[0].click();
assertFalse(actionButton.disabled);
dialogItems[0].click();
assertTrue(actionButton.disabled);
// Check multiple languages.
dialogItems[0].click(); // en.
dialogItems[2].click(); // tk.
assertFalse(actionButton.disabled);
// The action button should close and remove the dialog, enabling the
// checked languages.
actionButton.click();
assertEquals(
initialLanguages + ',en,tk',
languageHelper.getPref(languagesPref).value);
return dialogClosedResolver.promise;
});
// Test that searching languages works whether the displayed or native
// language name is queried.
test('search languages', function() {
const searchInput = dialog.$$('cr-search-field');
const getItems = function() {
return dialog.$.dialog.querySelectorAll('.list-item:not([hidden])');
};
// Expecting a few languages to be displayed when no query exists.
assertGE(getItems().length, 1);
// Issue query that matches the |displayedName|.
searchInput.setValue('greek');
Polymer.dom.flush();
assertEquals(1, getItems().length);
// Issue query that matches the |nativeDisplayedName|.
searchInput.setValue('Ελληνικά');
Polymer.dom.flush();
assertEquals(1, getItems().length);
// Issue query that does not match any language.
searchInput.setValue('egaugnal');
Polymer.dom.flush();
assertEquals(0, getItems().length);
// Issue query that should never match any language.
searchInput.setValue('_arc_ime_language_');
Polymer.dom.flush();
assertEquals(0, getItems().length);
});
test('Escape key behavior', function() {
const searchInput = dialog.$$('cr-search-field');
searchInput.setValue('dummyquery');
// Test that dialog is not closed if 'Escape' is pressed on the input
// and a search query exists.
MockInteractions.keyDownOn(searchInput, 19, [], 'Escape');
assertTrue(dialog.$.dialog.open);
// Test that dialog is closed if 'Escape' is pressed on the input and no
// search query exists.
searchInput.setValue('');
MockInteractions.keyDownOn(searchInput, 19, [], 'Escape');
assertFalse(dialog.$.dialog.open);
});
});
suite(TestNames.LanguageMenu, function() {
/*
* Finds, asserts and returns the menu item for the given i18n key.
* @param {string} i18nKey Name of the i18n string for the item's text.
* @return {!HTMLElement} Menu item.
*/
function getMenuItem(i18nKey) {
const i18nString = assert(loadTimeData.getString(i18nKey));
const menuItems = actionMenu.querySelectorAll('.dropdown-item');
const menuItem = Array.from(menuItems).find(
item => item.textContent.trim() == i18nString);
return assert(menuItem, 'Menu item "' + i18nKey + '" not found');
}
/*
* Checks the visibility of each expected menu item button.
* param {!Object<boolean>} Dictionary from i18n keys to expected
* visibility of those menu items.
*/
function assertMenuItemButtonsVisible(buttonVisibility) {
assertTrue(actionMenu.open);
for (const buttonKey of Object.keys(buttonVisibility)) {
const buttonItem = getMenuItem(buttonKey);
assertEquals(
!buttonVisibility[buttonKey], buttonItem.hidden,
'Menu item "' + buttonKey + '" hidden');
}
}
test('structure', function() {
const languageOptionsDropdownTrigger =
languagesCollapse.querySelector('button');
assertTrue(!!languageOptionsDropdownTrigger);
languageOptionsDropdownTrigger.click();
assertTrue(actionMenu.open);
const separator = actionMenu.querySelector('hr');
assertEquals(1, separator.offsetHeight);
// Disable Translate. On platforms that can't change the UI language,
// this hides all the checkboxes, so the separator isn't needed.
// Chrome OS and Windows still show a checkbox and thus the separator.
languageHelper.setPrefValue('translate.enabled', false);
assertEquals(
cr.isChromeOS || cr.isWindows ? 1 : 0, separator.offsetHeight);
});
test('test translate.enable toggle', function() {
const settingsToggle = languagesPage.$.offerTranslateOtherLanguages;
assertTrue(!!settingsToggle);
assertTrue(!!settingsToggle);
// Clicking on the toggle switches it to false.
settingsToggle.click();
let newToggleValue = languageHelper.prefs.translate.enabled.value;
assertFalse(newToggleValue);
// Clicking on the toggle switches it to true again.
settingsToggle.click();
newToggleValue = languageHelper.prefs.translate.enabled.value;
assertTrue(newToggleValue);
});
test('toggle translate for a specific language', function(done) {
// Open options for 'sw'.
const languageOptionsDropdownTrigger =
languagesCollapse.querySelectorAll('button')[1];
assertTrue(!!languageOptionsDropdownTrigger);
languageOptionsDropdownTrigger.click();
assertTrue(actionMenu.open);
// 'sw' supports translate to the target language ('en').
const translateOption = getMenuItem('offerToTranslateInThisLanguage');
assertFalse(translateOption.disabled);
assertTrue(translateOption.checked);
// Toggle the translate option.
translateOption.click();
// Menu should stay open briefly.
assertTrue(actionMenu.open);
// Guaranteed to run later than the menu close delay.
setTimeout(function() {
assertFalse(actionMenu.open);
assertDeepEquals(
['en-US', 'sw'],
languageHelper.prefs.translate_blocked_languages.value);
done();
}, settings.kMenuCloseDelay + 1);
});
test('toggle translate for target language', function() {
// Open options for 'en'.
const languageOptionsDropdownTrigger =
languagesCollapse.querySelectorAll('button')[0];
assertTrue(!!languageOptionsDropdownTrigger);
languageOptionsDropdownTrigger.click();
assertTrue(actionMenu.open);
// 'en' does not support.
const translateOption = getMenuItem('offerToTranslateInThisLanguage');
assertTrue(translateOption.disabled);
});
test('disable translate hides language-specific option', function() {
// Disables translate.
languageHelper.setPrefValue('translate.enabled', false);
// Open options for 'sw'.
const languageOptionsDropdownTrigger =
languagesCollapse.querySelectorAll('button')[1];
assertTrue(!!languageOptionsDropdownTrigger);
languageOptionsDropdownTrigger.click();
assertTrue(actionMenu.open);
// The language-specific translation option should be hidden.
const translateOption = actionMenu.querySelector('#offerTranslations');
assertTrue(!!translateOption);
assertTrue(translateOption.hidden);
});
test('remove language when starting with 3 languages', function() {
// Enable a language which we can then disable.
languageHelper.enableLanguage('no');
// Populate the dom-repeat.
Polymer.dom.flush();
// Find the new language item.
const items = languagesCollapse.querySelectorAll('.list-item');
const domRepeat = assert(languagesCollapse.querySelector(
Polymer.DomRepeat ? 'dom-repeat' : 'template[is="dom-repeat"]'));
const item = Array.from(items).find(function(el) {
return domRepeat.itemForElement(el) &&
domRepeat.itemForElement(el).language.code == 'no';
});
// Open the menu and select Remove.
item.querySelector('button').click();
assertTrue(actionMenu.open);
const removeMenuItem = getMenuItem('removeLanguage');
assertFalse(removeMenuItem.disabled);
removeMenuItem.click();
assertFalse(actionMenu.open);
assertEquals(
initialLanguages, languageHelper.getPref(languagesPref).value);
});
test('remove language when starting with 2 languages', function() {
assertEquals(
initialLanguages, languageHelper.getPref(languagesPref).value);
const items = languagesCollapse.querySelectorAll('.list-item');
const domRepeat = assert(languagesCollapse.querySelector(
Polymer.DomRepeat ? 'dom-repeat' : 'template[is="dom-repeat"]'));
const item = Array.from(items).find(function(el) {
return domRepeat.itemForElement(el) &&
domRepeat.itemForElement(el).language.code == 'sw';
});
// Open the menu and select Remove.
item.querySelector('button').click();
assertTrue(actionMenu.open);
const removeMenuItem = getMenuItem('removeLanguage');
assertFalse(removeMenuItem.disabled);
removeMenuItem.click();
assertFalse(actionMenu.open);
assertEquals('en-US', languageHelper.getPref(languagesPref).value);
});
test('move up/down buttons', function() {
// Add several languages.
for (const language of ['en-CA', 'en-US', 'tk', 'no'])
languageHelper.enableLanguage(language);
Polymer.dom.flush();
const menuButtons = languagesCollapse.querySelectorAll(
'.list-item paper-icon-button-light.icon-more-vert');
// First language should not have "Move up" or "Move to top".
menuButtons[0].querySelector('button').click();
assertMenuItemButtonsVisible({
moveToTop: false,
moveUp: false,
moveDown: true,
});
actionMenu.close();
// Second language should not have "Move up".
menuButtons[1].querySelector('button').click();
assertMenuItemButtonsVisible({
moveToTop: true,
moveUp: false,
moveDown: true,
});
actionMenu.close();
// Middle languages should have all buttons.
menuButtons[2].querySelector('button').click();
assertMenuItemButtonsVisible({
moveToTop: true,
moveUp: true,
moveDown: true,
});
actionMenu.close();
// Last language should not have "Move down".
menuButtons[menuButtons.length - 1].querySelector('button').click();
assertMenuItemButtonsVisible({
moveToTop: true,
moveUp: true,
moveDown: false,
});
actionMenu.close();
});
});
test(TestNames.InputMethods, function() {
const inputMethodsCollapse = languagesPage.$.inputMethodsCollapse;
const inputMethodSettingsExist = !!inputMethodsCollapse;
if (cr.isChromeOS) {
assertTrue(inputMethodSettingsExist);
const manageInputMethodsButton =
inputMethodsCollapse.querySelector('#manageInputMethods');
manageInputMethodsButton.click();
assertTrue(!!languagesPage.$$('settings-manage-input-methods-page'));
} else {
assertFalse(inputMethodSettingsExist);
}
});
suite(TestNames.Spellcheck, function() {
test('structure', function() {
const spellCheckCollapse = languagesPage.$.spellCheckCollapse;
const spellCheckSettingsExist = !!spellCheckCollapse;
if (cr.isMac) {
assertFalse(spellCheckSettingsExist);
return;
}
assertTrue(spellCheckSettingsExist);
// The row button should have a secondary row specifying which
// language spell check is enabled for.
const triggerRow = languagesPage.$.spellCheckSubpageTrigger;
// en-US starts with spellcheck enabled, so the secondary row is
// populated.
assertTrue(triggerRow.classList.contains('two-line'));
assertLT(0, triggerRow.querySelector('.secondary').textContent.length);
triggerRow.click();
Polymer.dom.flush();
// Disable spellcheck for en-US.
const spellcheckLanguageToggle =
spellCheckCollapse.querySelector('cr-toggle[checked]');
assertTrue(!!spellcheckLanguageToggle);
spellcheckLanguageToggle.click();
assertFalse(spellcheckLanguageToggle.checked);
assertEquals(
0, languageHelper.prefs.spellcheck.dictionaries.value.length);
// Now the secondary row is empty, so it shouldn't be shown.
assertFalse(triggerRow.classList.contains('two-line'));
assertEquals(
0, triggerRow.querySelector('.secondary').textContent.length);
// Force-enable a language via policy.
languageHelper.setPrefValue('spellcheck.forced_dictionaries', ['nb']);
// The second row should no longer be empty.
assertTrue(triggerRow.classList.contains('two-line'));
assertLT(0, triggerRow.querySelector('.secondary').textContent.length);
// Sets |browser.enable_spellchecking| to |value| as if it was set by
// policy.
const setEnableSpellcheckingViaPolicy = function(value) {
const newPrefValue = {
key: 'browser.enable_spellchecking',
type: chrome.settingsPrivate.PrefType.BOOLEAN,
value: value,
enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
controlledBy: chrome.settingsPrivate.ControlledBy.DEVICE_POLICY
};
// First set the prefValue, then override the actual preference
// object in languagesPage. This is necessary, to avoid a mismatch
// between the settings state and |languagesPage.prefs|, which would
// cause the value to be reset in |languagesPage.prefs|.
languageHelper.setPrefValue('browser.enable_spellchecking', value);
languagesPage.set('prefs.browser.enable_spellchecking', newPrefValue);
};
// Force-disable spellchecking via policy.
setEnableSpellcheckingViaPolicy(false);
Polymer.dom.flush();
// The second row should not be empty.
assertTrue(triggerRow.classList.contains('two-line'));
assertLT(0, triggerRow.querySelector('.secondary').textContent.length);
// The policy indicator should be present.
assertTrue(!!triggerRow.querySelector('cr-policy-pref-indicator'));
// Force-enable spellchecking via policy, and ensure that the policy
// indicator is not present. |enable_spellchecking| can be forced to
// true by policy, but no indicator should be shown in that case.
setEnableSpellcheckingViaPolicy(true);
Polymer.dom.flush();
assertFalse(!!triggerRow.querySelector('cr-policy-pref-indicator'));
});
test('error handling', function() {
if (cr.isMac)
return;
const checkAllHidden = nodes => {
assertTrue(nodes.every(node => node.hidden));
};
const languageSettingsPrivate =
browserProxy.getLanguageSettingsPrivate();
const spellCheckCollapse = languagesPage.$.spellCheckCollapse;
const errorDivs = Array.from(
spellCheckCollapse.querySelectorAll('.name-with-error-list div'));
assertEquals(4, errorDivs.length);
checkAllHidden(errorDivs);
const retryButtons =
Array.from(spellCheckCollapse.querySelectorAll('paper-button'));
assertEquals(2, retryButtons.length);
checkAllHidden(retryButtons);
const languageCode =
languagesPage.get('languages.enabled.0.language.code');
languageSettingsPrivate.onSpellcheckDictionariesChanged.callListeners([
{languageCode, isReady: false, downloadFailed: true},
]);
Polymer.dom.flush();
assertFalse(errorDivs[0].hidden);
checkAllHidden(errorDivs.slice(1));
assertFalse(retryButtons[0].hidden);
assertTrue(retryButtons[1].hidden);
// Check that more information is provided when subsequent downloads
// fail.
const moreInfo = errorDivs[1];
assertTrue(moreInfo.hidden);
// No change when status is the same as last update.
const currentStatus =
languagesPage.get('languages.enabled.0.downloadDictionaryStatus');
languageSettingsPrivate.onSpellcheckDictionariesChanged.callListeners(
[currentStatus]);
Polymer.dom.flush();
assertTrue(moreInfo.hidden);
retryButtons[0].click();
Polymer.dom.flush();
assertFalse(moreInfo.hidden);
});
});
});
return {TestNames: TestNames};
});