Commit 6969175e authored by My Nguyen's avatar My Nguyen Committed by Commit Bot

[OsSettingsLanguages] Add edit dictionary subpage details

Copied over from languages_page/edit_dictionary_page.js
One change: use computed property for hasWords_, for this to work
iron-list can't be under template dom-if. Empty iron-list should not
have too much impact on performance.

Mock: http://go/cros-lang-settings-ux-slide#slide=28
and http://go/cros-lang-settings-ux-slide#slide=31

No word view: http://screen/34mebiVZeEXru8k
With words view: http://screen/9mDdiNFpPv4avpu

Bug: 1113439
Change-Id: Ide9edc3cbd95f055fd50109d1a17973152b3076e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2395903
Commit-Queue: My Nguyen <myy@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Reviewed-by: default avatarRegan Hsu <hsuregan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806523}
parent fdc54fbd
......@@ -322,6 +322,24 @@
<message name="IDS_OS_SETTINGS_LANGUAGES_EDIT_DICTIONARY_LABEL" translateable="false" desc="Label for the section for users to add custom words for no spell check.">
Customize spell check
</message>
<message name="IDS_OS_SETTINGS_LANGUAGES_EDIT_DICTIONARY_DESCRIPTION" translateable="false" desc="Description for the section for users to add custom words for no spell check.">
Add words you want spell check to skip
</message>
<message name="IDS_OS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_BUTTON_LABEL" translateable="false" desc="Description for the button for users to add custom words for no spell check.">
Add word
</message>
<message name="IDS_OS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_DUPLICATE_ERROR" translateable="false" desc="Error message displayed to the user when the word is duplicated in the text input used to add a new word to the custom spell check dictionary.">
Word already added
</message>
<message name="IDS_OS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_LENGTH_ERROR" translateable="false" desc="Error message displayed to the user when the word is too long in the text input used to add a new word to the custom spell check dictionary.">
Use 99 letters or fewer for new words
</message>
<message name="IDS_OS_SETTINGS_LANGUAGES_DELETE_DICTIONARY_WORD_TOOLTIP" translateable="false" desc="Description for the tooltip that appear when users hover over the icon to delete a custom words.">
Delete word
</message>
<message name="IDS_OS_SETTINGS_LANGUAGES_NO_DICTIONARY_WORDS_LABEL" translateable="false" desc="Description for the label that appear when there are no custom words in the dictionary.">
Saved custom words will appear here
</message>
<message name="IDS_OS_SETTINGS_LANGUAGES_LIST_TITLE" desc="Title for the list of the user's preferred written languages.">
Languages
</message>
......
......@@ -9,6 +9,7 @@ import './date_time_page/timezone_selector.m.js';
import './os_files_page/os_files_page.m.js';
import './os_languages_page/input_method_options_page.m.js';
import './os_languages_page/input_page.m.js';
import './os_languages_page/os_edit_dictionary_page.m.js';
import './os_languages_page/os_languages_page.m.js';
import './os_languages_page/os_languages_page_v2.m.js';
import './os_languages_page/os_languages_section.m.js';
......
......@@ -81,6 +81,13 @@ js_library("manage_input_methods_page") {
}
js_library("os_edit_dictionary_page") {
deps = [
"..:os_route",
"../..:global_scroll_target_behavior",
"../../languages_page:languages_browser_proxy",
"//ui/webui/resources/cr_elements/cr_button:cr_button",
"//ui/webui/resources/cr_elements/cr_input:cr_input",
]
}
js_library("os_languages_section") {
......@@ -264,6 +271,15 @@ js_library("os_add_languages_dialog.m") {
js_library("os_edit_dictionary_page.m") {
sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_languages_page/os_edit_dictionary_page.m.js" ]
deps = [
"..:os_route.m",
"../..:global_scroll_target_behavior.m",
"../../languages_page:languages_browser_proxy.m",
"//third_party/polymer/v3_0/components-chromium/iron-list:iron-list",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/cr_elements/cr_button:cr_button.m",
"//ui/webui/resources/cr_elements/cr_input:cr_input.m",
]
extra_deps = [ ":os_edit_dictionary_page_module" ]
}
......
<link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
<link rel="import" href="chrome://resources/cr_elements/icons.html">
<link rel="import" href="chrome://resources/html/i18n_behavior.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys/iron-a11y-keys.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
<link rel="import" href="../metrics_recorder.html">
<link rel="import" href="../os_route.html">
<link rel="import" href="../../global_scroll_target_behavior.html">
<link rel="import" href="../../settings_shared_css.html">
<link rel="import" href="../../languages_page/languages_browser_proxy.html">
<dom-module id="os-settings-edit-dictionary-page">
<template>
<style include="settings-shared">
:host {
display: flex;
flex-direction: column;
}
#newWord {
width: 100%;
--cr-input-width: 216px;
}
#newWord::part(row-container) {
justify-content: normal;
}
iron-list .word {
flex: 1;
padding-inline-start: 10px;
}
#list {
padding: 0 var(--cr-section-padding);
}
.list-item {
border-bottom: var(--cr-separator-line);
}
#noWordsLabel {
padding-top: 39px;
text-align: center;
}
</style>
<div class="cr-row continuation">
$i18n{editDictionaryDescription}
</div>
<div class="cr-row first">
<iron-a11y-keys id="keys" keys="enter esc"
on-keys-pressed="onKeysPress_">
</iron-a11y-keys>
<cr-input id="newWord" value="{{newWordValue_}}"
spellcheck="false">
<cr-button on-click="onAddWordTap_" id="addWord" slot="suffix"
disabled="[[disableAddButton_]]">
$i18n{addDictionaryWordButtonLabel}
</cr-button>
</cr-input>
</div>
<iron-list id="list" items="[[words_]]" preserve-focus
scroll-target="[[subpageScrollTarget]]" risk-selection>
<template>
<div class="list-item">
<div id$="word[[index]]" class="word text-elide" aria-hidden="true">
[[item]]
</div>
<cr-icon-button class="icon-clear" on-click="onRemoveWordTap_"
tabindex$="[[tabIndex]]"
title="$i18n{deleteDictionaryWordTooltip}"
aria-describedby$="word[[index]]">
</cr-icon-button>
</div>
</template>
</iron-list>
<div id="noWordsLabel" class="secondary" hidden="[[hasWords_]]">
$i18n{noDictionaryWordsLabel}
</div>
</template>
<script src="os_edit_dictionary_page.js"></script>
</dom-module>
\ No newline at end of file
</dom-module>
......@@ -7,6 +7,167 @@
* the "dictionary" of custom words used for spell check.
*/
// clang-format off
// #import {GlobalScrollTargetBehavior} from '../../global_scroll_target_behavior.m.js';
// #import {loadTimeData} from '../../i18n_setup.js';
// #import {PrefsBehavior} from '../../prefs/prefs_behavior.m.js';
// #import {LanguagesBrowserProxyImpl} from '../../languages_page/languages_browser_proxy.m.js';
// clang-format on
Polymer({
is: 'os-settings-edit-dictionary-page',
behaviors: [
I18nBehavior,
settings.GlobalScrollTargetBehavior,
],
properties: {
/** @private */
newWordValue_: {
type: String,
value: '',
},
/**
* Needed for GlobalScrollTargetBehavior.
* @override
*/
subpageRoute: {
type: Object,
value: settings.routes.OS_LANGUAGES_EDIT_DICTIONARY,
},
/** @private {!Array<string>} */
words_: {
type: Array,
value: [],
},
/** @private */
hasWords_: {
type: Boolean,
value: false,
computed: 'computeHasWords_(words_.length)',
},
/** @private */
disableAddButton_: {
type: Boolean,
value: true,
computed: 'shouldDisableAddButton_(newWordValue_)',
}
},
/** @private {?LanguageSettingsPrivate} */
languageSettingsPrivate_: null,
/** @override */
created() {
this.languageSettingsPrivate_ =
settings.LanguagesBrowserProxyImpl.getInstance()
.getLanguageSettingsPrivate();
},
/** @override */
ready() {
this.languageSettingsPrivate_.getSpellcheckWords(words => {
this.words_ = words;
});
this.languageSettingsPrivate_.onCustomDictionaryChanged.addListener(
this.onCustomDictionaryChanged_.bind(this));
// Add a key handler for the new-word input.
this.$.keys.target = this.$.newWord;
},
/**
* @return {boolean}
* @private
*/
computeHasWords_() {
return this.words_.length > 0;
},
/**
* Adds the word in the new-word input to the dictionary.
* @private
*/
addWordFromInput_() {
// Spaces are allowed, but removing leading and trailing whitespace.
const word = this.getTrimmedNewWord_();
this.newWordValue_ = '';
if (word) {
this.languageSettingsPrivate_.addSpellcheckWord(word);
settings.recordSettingChange();
}
},
/**
* @return {boolean}
* @private
*/
shouldDisableAddButton_() {
return !this.getTrimmedNewWord_().length;
},
/**
* @return {string}
* @private
*/
getTrimmedNewWord_() {
return this.newWordValue_.trim();
},
/**
* Handles tapping on the Add Word button.
* @private
*/
onAddWordTap_() {
this.addWordFromInput_();
this.$.newWord.focus();
},
/**
* Handles updates to the word list. Additions are unshifted to the top
* of the list so that users can see them easily.
* @param {!Array<string>} added
* @param {!Array<string>} removed
* @private
*/
onCustomDictionaryChanged_(added, removed) {
for (const word of removed) {
this.arrayDelete('words_', word);
}
for (const word of added) {
if (!this.words_.includes(word)) {
this.unshift('words_', word);
}
}
},
/**
* Handles Enter and Escape key presses for the new-word input.
* @param {!CustomEvent<!{key: string}>} e
* @private
*/
onKeysPress_(e) {
if (e.detail.key === 'enter' && !this.disableAddButton_) {
this.addWordFromInput_();
} else if (e.detail.key === 'esc') {
e.detail.keyboardEvent.target.value = '';
}
},
/**
* Handles tapping on a "Remove word" icon button.
* @param {!{model: !{item: string}}} e
* @private
*/
onRemoveWordTap_(e) {
this.languageSettingsPrivate_.removeSpellcheckWord(e.model.item);
settings.recordSettingChange();
}
});
......@@ -46,6 +46,7 @@ os_settings_namespace_rewrites = settings_namespace_rewrites +
"settings.KerberosConfigErrorCode|KerberosConfigErrorCode",
"settings.KerberosErrorType|KerberosErrorType",
"settings.kMenuCloseDelay|kMenuCloseDelay",
"settings.LanguagesBrowserProxy|LanguagesBrowserProxy",
"settings.LanguagesMetricsProxy|LanguagesMetricsProxy",
"settings.LanguagesPageInteraction|LanguagesPageInteraction",
"settings.MultiDeviceBrowserProxy|MultiDeviceBrowserProxy",
......@@ -115,6 +116,7 @@ os_settings_auto_imports = settings_auto_imports +
"chrome/browser/resources/settings/chromeos/parental_controls_page/parental_controls_browser_proxy.html|ParentalControlsBrowserProxy,ParentalControlsBrowserProxyImpl",
"chrome/browser/resources/settings/chromeos/route_origin_behavior.html|RouteOriginBehaviorImpl,RouteOriginBehavior",
"chrome/browser/resources/settings/controls/settings_dropdown_menu.html|DropdownMenuOptionList",
"chrome/browser/resources/settings/languages_page/languages_browser_proxy.html|LanguagesBrowserProxy,LanguagesBrowserProxyImpl",
"chrome/browser/resources/settings/lifetime_browser_proxy.html|LifetimeBrowserProxy,LifetimeBrowserProxyImpl",
"chrome/browser/resources/settings/people_page/account_manager_browser_proxy.html|AccountManagerBrowserProxy,AccountManagerBrowserProxyImpl,Account",
"chrome/browser/resources/settings/people_page/profile_info_browser_proxy.html|ProfileInfoBrowserProxyImpl,ProfileInfoBrowserProxy,ProfileInfo",
......
......@@ -728,6 +728,11 @@
use_base_dir="false"
compress="false"
type="BINDATA" />
<include name="IDR_OS_SETTINGS_GLOBAL_SCROLL_TARGET_BEHAVIOR_M_JS"
file="${root_gen_dir}/chrome/browser/resources/settings/global_scroll_target_behavior.m.js"
use_base_dir="false"
compress="false"
type="BINDATA" />
<include name="IDR_OS_SETTINGS_I18N_SETUP_JS"
file="i18n_setup.js"
compress="false"
......
......@@ -317,6 +317,18 @@ void AddInputPageStringsV2(content::WebUIDataSource* html_source) {
{"languagesDictionaryDownloadRetryDescription",
IDS_OS_SETTINGS_LANGUAGES_DICTIONARY_DOWNLOAD_RETRY_DESCRIPTION},
{"editDictionaryLabel", IDS_OS_SETTINGS_LANGUAGES_EDIT_DICTIONARY_LABEL},
{"editDictionaryDescription",
IDS_OS_SETTINGS_LANGUAGES_EDIT_DICTIONARY_DESCRIPTION},
{"addDictionaryWordButtonLabel",
IDS_OS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_BUTTON_LABEL},
{"addDictionaryWordDuplicateError",
IDS_OS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_DUPLICATE_ERROR},
{"addDictionaryWordLengthError",
IDS_OS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_LENGTH_ERROR},
{"deleteDictionaryWordTooltip",
IDS_OS_SETTINGS_LANGUAGES_DELETE_DICTIONARY_WORD_TOOLTIP},
{"noDictionaryWordsLabel",
IDS_OS_SETTINGS_LANGUAGES_NO_DICTIONARY_WORDS_LABEL},
};
AddLocalizedStringsBulk(html_source, kLocalizedStrings);
}
......
......@@ -288,6 +288,7 @@ if (include_js_tests) {
"$root_gen_dir/chrome/test/data/webui/settings/chromeos/multidevice_subpage_tests.m.js",
"$root_gen_dir/chrome/test/data/webui/settings/chromeos/nearby_share_receive_dialog_tests.m.js",
"$root_gen_dir/chrome/test/data/webui/settings/chromeos/nearby_share_subpage_tests.m.js",
"$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_edit_dictionary_page_test.m.js",
"$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_languages_page_tests.m.js",
"$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_languages_page_v2_tests.m.js",
"$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_reset_page_test.m.js",
......
......@@ -50,6 +50,7 @@ js_modulizer("modulize") {
"multidevice_subpage_tests.js",
"nearby_share_receive_dialog_tests.js",
"nearby_share_subpage_tests.js",
"os_edit_dictionary_page_test.js",
"os_languages_page_tests.js",
"os_languages_page_v2_tests.js",
"os_reset_page_test.js",
......
// Copyright 2020 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.
// clang-format off
// #import {LanguagesBrowserProxyImpl} from 'chrome://os-settings/chromeos/lazy_load.js';
// #import {CrSettingsPrefs} from 'chrome://os-settings/chromeos/os_settings.js';
// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
// #import {FakeLanguageSettingsPrivate} from '../fake_language_settings_private.m.js';
// #import {FakeSettingsPrivate} from '../fake_settings_private.m.js';
// #import {TestLanguagesBrowserProxy} from './test_os_languages_browser_proxy.m.js';
// clang-format on
suite('edit dictionary page', () => {
function getFakePrefs() {
return [
{
key: 'intl.app_locale',
type: chrome.settingsPrivate.PrefType.STRING,
value: 'en-US',
},
{
key: 'intl.accept_languages',
type: chrome.settingsPrivate.PrefType.STRING,
value: 'en-US,sw',
},
{
key: 'spellcheck.dictionaries',
type: chrome.settingsPrivate.PrefType.LIST,
value: ['en-US'],
},
{
key: 'translate_blocked_languages',
type: chrome.settingsPrivate.PrefType.LIST,
value: ['en-US'],
},
{
key: 'settings.language.preferred_languages',
type: chrome.settingsPrivate.PrefType.STRING,
value: 'en-US,sw',
},
{
key: 'settings.language.preload_engines',
type: chrome.settingsPrivate.PrefType.STRING,
value: '_comp_ime_fgoepimhcoialccpbmpnnblemnepkkaoxkb:us::eng,' +
'_comp_ime_fgoepimhcoialccpbmpnnblemnepkkaoxkb:us:dvorak:eng',
},
{
key: 'settings.language.enabled_extension_imes',
type: chrome.settingsPrivate.PrefType.STRING,
value: '',
},
];
}
/** @type {?settings.SettingsEditDictionaryPageElement} */
let editDictPage;
/** @type {?settings.FakeLanguageSettingsPrivate} */
let languageSettingsPrivate;
/** @type {?settings.FakeSettingsPrivate} */
let settingsPrefs;
suiteSetup(() => {
CrSettingsPrefs.deferInitialization = true;
loadTimeData.overrideValues({enableLanguageSettingsV2: true});
});
setup(() => {
document.body.innerHTML = '';
settingsPrefs = document.createElement('settings-prefs');
const settingsPrivate = new settings.FakeSettingsPrivate(getFakePrefs());
settingsPrefs.initialize(settingsPrivate);
languageSettingsPrivate = new settings.FakeLanguageSettingsPrivate();
languageSettingsPrivate.setSettingsPrefs(settingsPrefs);
const browserProxy = new settings.TestLanguagesBrowserProxy();
settings.LanguagesBrowserProxyImpl.instance_ = browserProxy;
browserProxy.setLanguageSettingsPrivate(languageSettingsPrivate);
editDictPage = document.createElement('os-settings-edit-dictionary-page');
// Prefs would normally be data-bound to settings-languages.
document.body.appendChild(editDictPage);
});
test('adds word validation', () => {
// Check addWord enable/disable logic
const addWordButton = editDictPage.$.addWord;
assertTrue(!!addWordButton);
editDictPage.$.newWord.value = '';
assertTrue(addWordButton.disabled);
editDictPage.$.newWord.value = 'valid word';
assertFalse(addWordButton.disabled);
assertFalse(
window.getComputedStyle(addWordButton)['pointer-events'] ===
'none'); // Make sure add-word button actually clickable.
});
test('shows message when empty', () => {
assertTrue(!!editDictPage);
return languageSettingsPrivate.whenCalled('getSpellcheckWords').then(() => {
Polymer.dom.flush();
assertFalse(editDictPage.$.noWordsLabel.hidden);
});
});
test('adds words', () => {
const addWordButton = editDictPage.$$('#addWord');
editDictPage.$.newWord.value = 'valid word';
addWordButton.click();
editDictPage.$.newWord.value = 'valid word2';
addWordButton.click();
Polymer.dom.flush();
assertTrue(editDictPage.$.noWordsLabel.hidden);
assertTrue(!!editDictPage.$$('#list'));
const listItems = editDictPage.$$('#list').items;
assertEquals(2, listItems.length);
// list is shown with latest word added on top.
assertEquals('valid word2', listItems[0]);
assertEquals('valid word', listItems[1]);
});
test('removes word', () => {
const addWordButton = editDictPage.$$('#addWord');
editDictPage.$.newWord.value = 'valid word';
addWordButton.click();
Polymer.dom.flush();
assertTrue(!!editDictPage.$$('#list'));
assertEquals(1, editDictPage.$$('#list').items.length);
const removeWordButton = editDictPage.$$('cr-icon-button');
removeWordButton.click();
Polymer.dom.flush();
assertFalse(editDictPage.$.noWordsLabel.hidden);
assertTrue(!!editDictPage.$$('#list'));
assertEquals(0, editDictPage.$$('#list').items.length);
});
test('syncs removed and added words', () => {
languageSettingsPrivate.onCustomDictionaryChanged.callListeners(
/*added=*/['word1', 'word2', 'word3'], /*removed=*/[]);
Polymer.dom.flush();
assertTrue(!!editDictPage.$$('#list'));
let listItems = editDictPage.$$('#list').items;
assertEquals(3, listItems.length);
// list is shown with latest word added on top.
assertEquals('word3', listItems[0]);
assertEquals('word2', listItems[1]);
assertEquals('word1', listItems[2]);
languageSettingsPrivate.onCustomDictionaryChanged.callListeners(
/*added=*/['word4'], /*removed=*/['word2', 'word3']);
Polymer.dom.flush();
assertTrue(!!editDictPage.$$('#list'));
listItems = editDictPage.$$('#list').items;
assertEquals(2, listItems.length);
// list is shown with latest word added on top.
assertEquals('word4', listItems[0]);
assertEquals('word1', listItems[1]);
});
test('removes is in tab order', () => {
const addWordButton = editDictPage.$$('#addWord');
editDictPage.$.newWord.value = 'valid word';
addWordButton.click();
Polymer.dom.flush();
assertTrue(editDictPage.$.noWordsLabel.hidden);
assertTrue(!!editDictPage.$$('#list'));
assertEquals(1, editDictPage.$$('#list').items.length);
const removeWordButton = editDictPage.$$('cr-icon-button');
// Button should be reachable in the tab order.
assertEquals('0', removeWordButton.getAttribute('tabindex'));
removeWordButton.click();
Polymer.dom.flush();
assertFalse(editDictPage.$.noWordsLabel.hidden);
editDictPage.$.newWord.value = 'valid word2';
addWordButton.click();
Polymer.dom.flush();
assertTrue(editDictPage.$.noWordsLabel.hidden);
assertTrue(!!editDictPage.$$('#list'));
assertEquals(1, editDictPage.$$('#list').items.length);
const newRemoveWordButton = editDictPage.$$('cr-icon-button');
// Button should be reachable in the tab order.
assertEquals('0', newRemoveWordButton.getAttribute('tabindex'));
});
});
......@@ -1731,6 +1731,32 @@ var OSSettingsResetPageTest = class extends OSSettingsBrowserTest {
}
};
// eslint-disable-next-line no-var
var OSSettingsEditDictionaryPageTest = class extends OSSettingsBrowserTest {
/** @override */
get browsePreload() {
return super.browsePreload +
'chromeos/os_language_page/os_edit_dictionary_page.html';
}
/** @override */
get extraLibraries() {
return super.extraLibraries.concat([
BROWSER_SETTINGS_PATH + '../fake_chrome_event.js',
BROWSER_SETTINGS_PATH + '../test_browser_proxy.js',
BROWSER_SETTINGS_PATH + 'fake_input_method_private.js',
BROWSER_SETTINGS_PATH + 'fake_language_settings_private.js',
BROWSER_SETTINGS_PATH + 'fake_settings_private.js',
'os_edit_dictionary_page_test.js',
'test_os_languages_browser_proxy.js',
]);
}
};
TEST_F('OSSettingsEditDictionaryPageTest', 'AllJsTests', () => {
mocha.run();
});
TEST_F('OSSettingsResetPageTest', 'AllJsTests', () => {
mocha.run();
});
......
......@@ -82,6 +82,7 @@ TEST_F('OSSettingsOsLanguagesPageV2V3Test', 'All', () => mocha.run());
['MultidevicePage', 'multidevice_page_tests.m.js'],
['MultideviceSmartLockSubPage', 'multidevice_smartlock_subpage_test.m.js'],
['MultideviceSubPage', 'multidevice_subpage_tests.m.js'],
['OsEditDictionaryPage', 'os_edit_dictionary_page_test.m.js'],
['OsLanguagesPage', 'os_languages_page_tests.m.js'],
['NearbyShareReceiveDialog', 'nearby_share_receive_dialog_tests.m.js'],
['NearbyShareSubPage', 'nearby_share_subpage_tests.m.js'],
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment