blob: 22ef23ed570f919a66c780f7212e96caa348c835 [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');
MockInteractions.tap(addLanguagesButton);
// The page stamps the dialog, registers listeners, and populates the
// iron-list asynchronously at microtask timing, so wait for a new task.
setTimeout(function() {
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'));
// 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.
MockInteractions.tap(cancelButton);
return dialogClosedResolver.promise;
});
test('add languages and cancel', function() {
// Check some languages.
MockInteractions.tap(dialogItems[1]); // en-CA.
MockInteractions.tap(dialogItems[2]); // tk.
// Canceling the dialog should close and remove it without enabling
// the checked languages.
MockInteractions.tap(cancelButton);
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.
MockInteractions.tap(actionButton);
Polymer.dom.flush();
assertEquals(dialog, languagesPage.$$('settings-add-languages-dialog'));
// Check and uncheck one language.
MockInteractions.tap(dialogItems[0]);
assertFalse(actionButton.disabled);
MockInteractions.tap(dialogItems[0]);
assertTrue(actionButton.disabled);
// Check multiple languages.
MockInteractions.tap(dialogItems[0]); // en.
MockInteractions.tap(dialogItems[2]); // tk.
assertFalse(actionButton.disabled);
// The action button should close and remove the dialog, enabling the
// checked languages.
MockInteractions.tap(actionButton);
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.$$('settings-subpage-search');
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);
});
});
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);
MockInteractions.tap(languageOptionsDropdownTrigger);
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.
MockInteractions.tap(settingsToggle);
let newToggleValue = languageHelper.prefs.translate.enabled.value;
assertFalse(newToggleValue);
// Clicking on the toggle switches it to true again.
MockInteractions.tap(settingsToggle);
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);
MockInteractions.tap(languageOptionsDropdownTrigger);
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.
MockInteractions.tap(translateOption);
// 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('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);
MockInteractions.tap(languageOptionsDropdownTrigger);
assertTrue(actionMenu.open);
// The language-specific translation option should be hidden.
const translateOption = actionMenu.querySelector('#offerTranslations');
assertTrue(!!translateOption);
assertTrue(translateOption.hidden);
});
test('remove language', 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('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.
MockInteractions.tap(item.querySelector('button'));
assertTrue(actionMenu.open);
const removeMenuItem = getMenuItem('removeLanguage');
assertFalse(removeMenuItem.disabled);
MockInteractions.tap(removeMenuItem);
assertFalse(actionMenu.open);
assertEquals(
initialLanguages, 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 button.icon-more-vert');
// First language should not have "Move up" or "Move to top".
MockInteractions.tap(menuButtons[0]);
assertMenuItemButtonsVisible({
moveToTop: false, moveUp: false, moveDown: true,
});
actionMenu.close();
// Second language should not have "Move up".
MockInteractions.tap(menuButtons[1]);
assertMenuItemButtonsVisible({
moveToTop: true, moveUp: false, moveDown: true,
});
actionMenu.close();
// Middle languages should have all buttons.
MockInteractions.tap(menuButtons[2]);
assertMenuItemButtonsVisible({
moveToTop: true, moveUp: true, moveDown: true,
});
actionMenu.close();
// Last language should not have "Move down".
MockInteractions.tap(menuButtons[menuButtons.length - 1]);
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');
MockInteractions.tap(manageInputMethodsButton);
assertTrue(!!languagesPage.$$('settings-manage-input-methods-page'));
} else {
assertFalse(inputMethodSettingsExist);
}
});
test(TestNames.Spellcheck, function() {
const spellCheckCollapse = languagesPage.$.spellCheckCollapse;
const spellCheckSettingsExist = !!spellCheckCollapse;
if (cr.isMac) {
assertFalse(spellCheckSettingsExist);
} else {
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);
MockInteractions.tap(triggerRow);
Polymer.dom.flush();
// Disable spellcheck for en-US.
const spellcheckLanguageToggle =
spellCheckCollapse.querySelector('cr-toggle[checked]');
assertTrue(!!spellcheckLanguageToggle);
MockInteractions.tap(spellcheckLanguageToggle);
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);
// Force-disable spellchecking via policy.
languageHelper.setPrefValue('browser.enable_spellchecking', 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.
languageHelper.setPrefValue('browser.enable_spellchecking', true);
Polymer.dom.flush();
assertFalse(!!triggerRow.querySelector('cr-policy-pref-indicator'));
}
});
});
return {TestNames: TestNames};
});