lighthouse 10.0.0-dev.20230213 → 10.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/audits/dobetterweb/doctype.d.ts +3 -2
- package/core/audits/dobetterweb/doctype.js +15 -4
- package/core/audits/dobetterweb/paste-preventing-inputs.js +2 -2
- package/core/gather/driver/network-monitor.js +1 -3
- package/core/gather/gatherers/bf-cache-failures.js +7 -8
- package/package.json +1 -1
- package/shared/localization/locales/en-US.json +1 -1
- package/shared/localization/locales/en-XL.json +1 -1
- package/.codecov.yml +0 -22
- package/.cz-config.js +0 -24
- package/.gitattributes +0 -5
- package/.mailmap +0 -6
- package/CODE_OF_CONDUCT.md +0 -93
- package/eslint-local-rules.cjs +0 -106
- package/flow-report/test/app-test.d.ts +0 -7
- package/flow-report/test/app-test.tsx +0 -52
- package/flow-report/test/common-test.d.ts +0 -7
- package/flow-report/test/common-test.tsx +0 -103
- package/flow-report/test/flow-report-pptr-test.d.ts +0 -7
- package/flow-report/test/flow-report-pptr-test.ts +0 -59
- package/flow-report/test/header-test.d.ts +0 -7
- package/flow-report/test/header-test.tsx +0 -55
- package/flow-report/test/run-flow-report-tests.sh +0 -20
- package/flow-report/test/sample-flow.d.ts +0 -7
- package/flow-report/test/sample-flow.ts +0 -17
- package/flow-report/test/setup/env-setup.d.ts +0 -11
- package/flow-report/test/setup/env-setup.ts +0 -55
- package/flow-report/test/sidebar/flow-test.d.ts +0 -7
- package/flow-report/test/sidebar/flow-test.tsx +0 -65
- package/flow-report/test/sidebar/sidebar-test.d.ts +0 -7
- package/flow-report/test/sidebar/sidebar-test.tsx +0 -116
- package/flow-report/test/summary/category-test.d.ts +0 -7
- package/flow-report/test/summary/category-test.tsx +0 -206
- package/flow-report/test/summary/summary-test.d.ts +0 -7
- package/flow-report/test/summary/summary-test.tsx +0 -124
- package/flow-report/test/topbar-test.d.ts +0 -7
- package/flow-report/test/topbar-test.tsx +0 -84
- package/flow-report/test/util-test.d.ts +0 -7
- package/flow-report/test/util-test.tsx +0 -133
- package/flow-report/test/wrappers/category-score-test.d.ts +0 -7
- package/flow-report/test/wrappers/category-score-test.tsx +0 -84
- package/flow-report/test/wrappers/markdown-test.d.ts +0 -7
- package/flow-report/test/wrappers/markdown-test.tsx +0 -17
- package/report/test/clients/bundle-test.js +0 -65
- package/report/test/generator/file-namer-test.js +0 -27
- package/report/test/generator/report-generator-test.js +0 -160
- package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +0 -15
- package/report/test/renderer/category-renderer-test.js +0 -575
- package/report/test/renderer/components-test.js +0 -124
- package/report/test/renderer/crc-details-renderer-test.js +0 -116
- package/report/test/renderer/details-renderer-test.js +0 -882
- package/report/test/renderer/dom-test.js +0 -257
- package/report/test/renderer/element-screenshot-renderer-test.js +0 -183
- package/report/test/renderer/i18n-formatter-test.js +0 -179
- package/report/test/renderer/performance-category-renderer-test.js +0 -417
- package/report/test/renderer/pwa-category-renderer-test.js +0 -301
- package/report/test/renderer/report-renderer-axe-test.js +0 -85
- package/report/test/renderer/report-renderer-test.js +0 -268
- package/report/test/renderer/report-ui-features-test.js +0 -773
- package/report/test/renderer/report-utils-test.js +0 -219
- package/report/test/renderer/snippet-renderer-test.js +0 -291
- package/report/test/renderer/text-encoding-test.js +0 -43
- package/report/test-assets/faux-psi-template.html +0 -177
- package/report/test-assets/faux-psi.d.ts +0 -34
- package/report/test-assets/faux-psi.js +0 -168
- package/report/test-assets/lhr-3.0.0.json +0 -2332
- package/report/test-assets/lhr-4.3.0.json +0 -4754
- package/report/test-assets/lhr-5.0.0.json +0 -5994
- package/report/test-assets/lhr-6.0.0.json +0 -7222
- package/report/test-assets/lhr-8.5.0.json +0 -8679
- package/shared/test/localization/format-test.js +0 -430
- package/shared/test/localization/locales-test.js +0 -45
- package/shared/test/localization/swap-locale-test.js +0 -127
- package/shared/test/util-test.js +0 -214
- package/vercel.json +0 -6
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license Copyright 2018 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
-
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import path from 'path';
|
|
8
|
-
|
|
9
|
-
import * as i18n from '../../../core/lib/i18n/i18n.js';
|
|
10
|
-
import * as constants from '../../../core/config/constants.js';
|
|
11
|
-
import * as format from '../../localization/format.js';
|
|
12
|
-
import {locales} from '../../localization/locales.js';
|
|
13
|
-
import {getModuleDirectory, getModulePath} from '../../../esm-utils.js';
|
|
14
|
-
import {LH_ROOT} from '../../../root.js';
|
|
15
|
-
|
|
16
|
-
const moduleDir = getModuleDirectory(import.meta);
|
|
17
|
-
const modulePath = getModulePath(import.meta);
|
|
18
|
-
|
|
19
|
-
describe('format', () => {
|
|
20
|
-
describe('DEFAULT_LOCALE', () => {
|
|
21
|
-
it('is the same as the default config locale', () => {
|
|
22
|
-
expect(format.DEFAULT_LOCALE).toBe(constants.defaultSettings.locale);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('#getAvailableLocales', () => {
|
|
27
|
-
it('has all the available locales', () => {
|
|
28
|
-
const availableLocales = format.getAvailableLocales();
|
|
29
|
-
for (const locale of ['en', 'es', 'ru', 'zh']) {
|
|
30
|
-
expect(availableLocales).toContain(locale);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const rawLocales = Object.keys(locales).sort();
|
|
34
|
-
expect(availableLocales.sort()).toEqual(rawLocales);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('contains the default locale', () => {
|
|
38
|
-
expect(format.getAvailableLocales()).toContain(format.DEFAULT_LOCALE);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('#getCanonicalLocales', () => {
|
|
43
|
-
it('contains some canonical locales', () => {
|
|
44
|
-
const canonicalLocales = format.getCanonicalLocales();
|
|
45
|
-
for (const locale of ['en-US', 'es', 'ru', 'zh']) {
|
|
46
|
-
expect(canonicalLocales).toContain(locale);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('is a subset of the available locales', () => {
|
|
51
|
-
const canonicalLocales = format.getCanonicalLocales();
|
|
52
|
-
const availableLocales = format.getAvailableLocales();
|
|
53
|
-
|
|
54
|
-
for (const canonicalLocale of canonicalLocales) {
|
|
55
|
-
expect(availableLocales).toContain(canonicalLocale);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
expect(canonicalLocales.length).toBeLessThan(availableLocales.length);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('#_formatPathAsString', () => {
|
|
63
|
-
it('handles simple paths', () => {
|
|
64
|
-
expect(format._formatPathAsString(['foo'])).toBe('foo');
|
|
65
|
-
expect(format._formatPathAsString(['foo', 'bar', 'baz'])).toBe('foo.bar.baz');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('handles array paths', () => {
|
|
69
|
-
expect(format._formatPathAsString(['foo', 0])).toBe('foo[0]');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('handles complex paths', () => {
|
|
73
|
-
const propertyPath = ['foo', 'what-the', 'bar', 0, 'no'];
|
|
74
|
-
expect(format._formatPathAsString(propertyPath)).toBe('foo[what-the].bar[0].no');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('throws on unhandleable paths', () => {
|
|
78
|
-
expect(() => format._formatPathAsString(['Bobby "DROP TABLE'])).toThrow(/Cannot handle/);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe('#replaceIcuMessages', () => {
|
|
83
|
-
it('replaces the references in the LHR', () => {
|
|
84
|
-
const fakeFile = path.join(moduleDir, 'fake-file-number-2.js');
|
|
85
|
-
const UIStrings = {aString: 'different {x}!'};
|
|
86
|
-
const formatter = i18n.createIcuMessageFn(fakeFile, UIStrings);
|
|
87
|
-
|
|
88
|
-
const title = formatter(UIStrings.aString, {x: 1});
|
|
89
|
-
const lhr = {audits: {'fake-audit': {title}}};
|
|
90
|
-
|
|
91
|
-
const icuMessagePaths = format.replaceIcuMessages(lhr, 'en-US');
|
|
92
|
-
expect(lhr.audits['fake-audit'].title).toBe('different 1!');
|
|
93
|
-
|
|
94
|
-
const expectedPathId = 'shared/test/localization/fake-file-number-2.js | aString';
|
|
95
|
-
expect(icuMessagePaths).toEqual({
|
|
96
|
-
[expectedPathId]: [{path: 'audits[fake-audit].title', values: {x: 1}}]});
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe('#getRendererFormattedStrings', () => {
|
|
101
|
-
it('returns icu messages in the specified locale', () => {
|
|
102
|
-
const strings = format.getRendererFormattedStrings('en-XA');
|
|
103
|
-
expect(strings.passedAuditsGroupTitle).toEqual('[Þåššéð åûðîţš one two]');
|
|
104
|
-
expect(strings.snippetCollapseButtonLabel).toEqual('[Çöļļåþšé šñîþþéţ one two]');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('throws an error for invalid locales', () => {
|
|
108
|
-
expect(_ => format.getRendererFormattedStrings('not-a-locale'))
|
|
109
|
-
.toThrow(`Unsupported locale 'not-a-locale'`);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
describe('#getFormatted', () => {
|
|
114
|
-
it('returns the formatted string', () => {
|
|
115
|
-
const UIStrings = {testMessage: 'happy test'};
|
|
116
|
-
const str_ = i18n.createIcuMessageFn(modulePath, UIStrings);
|
|
117
|
-
const formattedStr = format.getFormatted(str_(UIStrings.testMessage), 'en');
|
|
118
|
-
expect(formattedStr).toEqual('happy test');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('returns the formatted string with replacements', () => {
|
|
122
|
-
const UIStrings = {testMessage: 'replacement test ({errorCode})'};
|
|
123
|
-
const str_ = i18n.createIcuMessageFn(modulePath, UIStrings);
|
|
124
|
-
const formattedStr = format.getFormatted(str_(UIStrings.testMessage,
|
|
125
|
-
{errorCode: 'BOO'}), 'en');
|
|
126
|
-
expect(formattedStr).toEqual('replacement test (BOO)');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('throws an error for invalid locales', () => {
|
|
130
|
-
// Populate a string to try to localize to a bad locale.
|
|
131
|
-
const UIStrings = {testMessage: 'testy test'};
|
|
132
|
-
const str_ = i18n.createIcuMessageFn(modulePath, UIStrings);
|
|
133
|
-
|
|
134
|
-
expect(_ => format.getFormatted(str_(UIStrings.testMessage), 'still-not-a-locale'))
|
|
135
|
-
.toThrow(`Unsupported locale 'still-not-a-locale'`);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('does not alter the passed-in replacement values object', () => {
|
|
139
|
-
const UIStrings = {
|
|
140
|
-
testMessage: 'needs {count, number, bytes}KB test {str} in {timeInMs, number, seconds}s',
|
|
141
|
-
};
|
|
142
|
-
const str_ = i18n.createIcuMessageFn(modulePath, UIStrings);
|
|
143
|
-
|
|
144
|
-
const replacements = {
|
|
145
|
-
count: 2555,
|
|
146
|
-
str: '*units*',
|
|
147
|
-
timeInMs: 314159265,
|
|
148
|
-
};
|
|
149
|
-
const replacementsClone = JSON.parse(JSON.stringify(replacements));
|
|
150
|
-
|
|
151
|
-
const formattedStr = format.getFormatted(str_(UIStrings.testMessage, replacements), 'en');
|
|
152
|
-
expect(formattedStr).toEqual('needs 2KB test *units* in 314,159.3s');
|
|
153
|
-
|
|
154
|
-
expect(replacements).toEqual(replacementsClone);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('returns a message that is already a string unchanged', () => {
|
|
158
|
-
const testString = 'kind of looks like it needs ({formatting})';
|
|
159
|
-
const formattedStr = format.getFormatted(testString, 'pl');
|
|
160
|
-
expect(formattedStr).toBe(testString);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('throws an error if formatting something other than IcuMessages or strings', () => {
|
|
164
|
-
expect(_ => format.getFormatted(15, 'lt'))
|
|
165
|
-
.toThrow(`Attempted to format invalid icuMessage type`);
|
|
166
|
-
expect(_ => format.getFormatted(new Date(), 'sr-Latn'))
|
|
167
|
-
.toThrow(`Attempted to format invalid icuMessage type`);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
describe('#registerLocaleData', () => {
|
|
172
|
-
// Store original locale data so we can restore at the end
|
|
173
|
-
const clonedLocales = JSON.parse(JSON.stringify(locales));
|
|
174
|
-
|
|
175
|
-
it('installs new locale strings', () => {
|
|
176
|
-
const localeData = {
|
|
177
|
-
'shared/test/localization/format-test.js | testString': {
|
|
178
|
-
'message': 'en-XZ cuerda!',
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
format.registerLocaleData('en-XZ', localeData);
|
|
182
|
-
|
|
183
|
-
const UIStrings = {testString: 'en-US string!'};
|
|
184
|
-
const str_ = i18n.createIcuMessageFn(modulePath, UIStrings);
|
|
185
|
-
const formattedStr = format.getFormatted(str_(UIStrings.testString), 'en-XZ');
|
|
186
|
-
expect(formattedStr).toEqual('en-XZ cuerda!');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
[
|
|
190
|
-
{label: 'relative path', filename: 'core/audits/is-on-https.js'},
|
|
191
|
-
{label: 'absolute path', filename: `${LH_ROOT}/core/audits/is-on-https.js`},
|
|
192
|
-
].forEach(({label, filename}) => {
|
|
193
|
-
it(`overwrites existing locale strings: ${label}`, async () => {
|
|
194
|
-
const {UIStrings} = await import('../../../core/audits/is-on-https.js');
|
|
195
|
-
const str_ = i18n.createIcuMessageFn(filename, UIStrings);
|
|
196
|
-
|
|
197
|
-
// To start with, we get back the intended string..
|
|
198
|
-
const origTitle = format.getFormatted(str_(UIStrings.title), 'es-419');
|
|
199
|
-
expect(origTitle).toEqual('Usa HTTPS');
|
|
200
|
-
const origFailureTitle = format.getFormatted(str_(UIStrings.failureTitle), 'es-419');
|
|
201
|
-
expect(origFailureTitle).toEqual('No usa HTTPS');
|
|
202
|
-
|
|
203
|
-
// Now we declare and register the new string...
|
|
204
|
-
const localeData = {
|
|
205
|
-
'core/audits/is-on-https.js | title': {
|
|
206
|
-
'message': 'new string for es-419 uses https!',
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
format.registerLocaleData('es-419', localeData);
|
|
210
|
-
|
|
211
|
-
// And confirm that's what is returned
|
|
212
|
-
const newTitle = format.getFormatted(str_(UIStrings.title), 'es-419');
|
|
213
|
-
expect(newTitle).toEqual('new string for es-419 uses https!');
|
|
214
|
-
|
|
215
|
-
// Meanwhile another string that wasn't set in registerLocaleData just falls back to english
|
|
216
|
-
const newFailureTitle = format.getFormatted(str_(UIStrings.failureTitle), 'es-419');
|
|
217
|
-
expect(newFailureTitle).toEqual('Does not use HTTPS');
|
|
218
|
-
|
|
219
|
-
// Restore overwritten strings to avoid messing with other tests
|
|
220
|
-
locales['es-419'] = clonedLocales['es-419'];
|
|
221
|
-
const title = format.getFormatted(str_(UIStrings.title), 'es-419');
|
|
222
|
-
expect(title).toEqual('Usa HTTPS');
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
describe('#isIcuMessage', () => {
|
|
228
|
-
const icuMessage = {
|
|
229
|
-
i18nId: 'shared/test/localization/fake-file.js | title',
|
|
230
|
-
values: {x: 1},
|
|
231
|
-
formattedDefault: 'a default',
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
it('passes a valid LH.IcuMessage', () => {
|
|
235
|
-
expect(format.isIcuMessage(icuMessage)).toBe(true);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('fails non-objects', () => {
|
|
239
|
-
expect(format.isIcuMessage(undefined)).toBe(false);
|
|
240
|
-
expect(format.isIcuMessage(null)).toBe(false);
|
|
241
|
-
expect(format.isIcuMessage('ICU!')).toBe(false);
|
|
242
|
-
expect(format.isIcuMessage(55)).toBe(false);
|
|
243
|
-
expect(format.isIcuMessage([
|
|
244
|
-
icuMessage,
|
|
245
|
-
icuMessage,
|
|
246
|
-
])).toBe(false);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('fails invalid or missing i18nIds', () => {
|
|
250
|
-
const badIdMessage = {...icuMessage, i18nId: 0};
|
|
251
|
-
expect(format.isIcuMessage(badIdMessage)).toBe(false);
|
|
252
|
-
|
|
253
|
-
const noIdMessage = {...icuMessage};
|
|
254
|
-
delete noIdMessage.i18nId;
|
|
255
|
-
expect(format.isIcuMessage(noIdMessage)).toBe(false);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('fails invalid or missing formattedDefault', () => {
|
|
259
|
-
const badDefaultMessage = {...icuMessage, formattedDefault: -0};
|
|
260
|
-
expect(format.isIcuMessage(badDefaultMessage)).toBe(false);
|
|
261
|
-
|
|
262
|
-
const noDefaultMessage = {...icuMessage};
|
|
263
|
-
delete noDefaultMessage.formattedDefault;
|
|
264
|
-
expect(format.isIcuMessage(noDefaultMessage)).toBe(false);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it('passes missing values', () => {
|
|
268
|
-
const emptyValuesMessage = {...icuMessage, values: {}};
|
|
269
|
-
expect(format.isIcuMessage(emptyValuesMessage)).toBe(true);
|
|
270
|
-
|
|
271
|
-
const noValuesMessage = {...icuMessage};
|
|
272
|
-
delete noValuesMessage.values;
|
|
273
|
-
expect(format.isIcuMessage(noValuesMessage)).toBe(true);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('fails invalid values types', () => {
|
|
277
|
-
const badValuesMessage = {...icuMessage, values: NaN};
|
|
278
|
-
expect(format.isIcuMessage(badValuesMessage)).toBe(false);
|
|
279
|
-
const nullValuesMessage = {...icuMessage, values: null};
|
|
280
|
-
expect(format.isIcuMessage(nullValuesMessage)).toBe(false);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it(`fails invalid values' values types`, () => {
|
|
284
|
-
const badValuesValuesMessage = {...icuMessage, values: {a: false}};
|
|
285
|
-
expect(format.isIcuMessage(badValuesValuesMessage)).toBe(false);
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
describe('#getIcuMessageIdParts', () => {
|
|
290
|
-
it('returns valid ICU message id parts', () => {
|
|
291
|
-
const {filename, key} = format.getIcuMessageIdParts('path/to/file.js | modeName');
|
|
292
|
-
expect(filename).toEqual('path/to/file.js');
|
|
293
|
-
expect(key).toEqual('modeName');
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('throws on invalid ICU message id', () => {
|
|
297
|
-
expect(() => {
|
|
298
|
-
format.getIcuMessageIdParts('path/to/file.js');
|
|
299
|
-
}).toThrow();
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
describe('Message values are properly formatted', () => {
|
|
304
|
-
// Message strings won't be in locale files, so will fall back to values given here.
|
|
305
|
-
const UIStrings = {
|
|
306
|
-
helloWorld: 'Hello World',
|
|
307
|
-
helloBytesWorld: 'Hello {in, number, bytes} World',
|
|
308
|
-
helloMsWorld: 'Hello {in, number, milliseconds} World',
|
|
309
|
-
helloSecWorld: 'Hello {in, number, seconds} World',
|
|
310
|
-
helloTimeInMsWorld: 'Hello {timeInMs, number, seconds} World',
|
|
311
|
-
helloPercentWorld: 'Hello {in, number, extendedPercent} World',
|
|
312
|
-
helloWorldMultiReplace: '{hello} {world}',
|
|
313
|
-
helloPlural: '{itemCount, plural, =1{1 hello} other{hellos}}',
|
|
314
|
-
helloPluralNestedICU: '{itemCount, plural, ' +
|
|
315
|
-
'=1{1 hello {in, number, bytes}} ' +
|
|
316
|
-
'other{hellos {in, number, bytes}}}',
|
|
317
|
-
helloPluralNestedPluralAndICU: '{itemCount, plural, ' +
|
|
318
|
-
'=1{{innerItemCount, plural, ' +
|
|
319
|
-
'=1{1 hello 1 goodbye {in, number, bytes}} ' +
|
|
320
|
-
'other{1 hello, goodbyes {in, number, bytes}}}} ' +
|
|
321
|
-
'other{{innerItemCount, plural, ' +
|
|
322
|
-
'=1{hellos 1 goodbye {in, number, bytes}} ' +
|
|
323
|
-
'other{hellos, goodbyes {in, number, bytes}}}}}',
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
let str_;
|
|
327
|
-
before(() => {
|
|
328
|
-
str_ = i18n.createIcuMessageFn(modulePath, UIStrings);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it('formats a basic message', () => {
|
|
332
|
-
const helloStr = str_(UIStrings.helloWorld);
|
|
333
|
-
expect(helloStr).toBeDisplayString('Hello World');
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it('formats a message with bytes', () => {
|
|
337
|
-
const helloBytesStr = str_(UIStrings.helloBytesWorld, {in: 1875});
|
|
338
|
-
expect(helloBytesStr).toBeDisplayString('Hello 2 World');
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it('formats a message with milliseconds', () => {
|
|
342
|
-
const helloMsStr = str_(UIStrings.helloMsWorld, {in: 432});
|
|
343
|
-
expect(helloMsStr).toBeDisplayString('Hello 430 World');
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it('formats a message with seconds', () => {
|
|
347
|
-
const helloSecStr = str_(UIStrings.helloSecWorld, {in: 753});
|
|
348
|
-
expect(helloSecStr).toBeDisplayString('Hello 753.0 World');
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('formats a message with seconds timeInMs', () => {
|
|
352
|
-
const helloTimeInMsStr = str_(UIStrings.helloTimeInMsWorld, {timeInMs: 753543});
|
|
353
|
-
expect(helloTimeInMsStr).toBeDisplayString('Hello 753.5 World');
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it('formats a message with extended percent', () => {
|
|
357
|
-
const helloPercentStr = str_(UIStrings.helloPercentWorld, {in: 0.43078});
|
|
358
|
-
expect(helloPercentStr).toBeDisplayString('Hello 43.08% World');
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it('throws an error when values are needed but not provided', () => {
|
|
362
|
-
expect(_ => format.getFormatted(str_(UIStrings.helloBytesWorld), 'en-US'))
|
|
363
|
-
// eslint-disable-next-line max-len
|
|
364
|
-
.toThrow(`ICU Message "Hello {in, number, bytes} World" contains a value reference ("in") that wasn't provided`);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it('throws an error when a value is missing', () => {
|
|
368
|
-
expect(_ => format.getFormatted(str_(UIStrings.helloWorldMultiReplace,
|
|
369
|
-
{hello: 'hello'}), 'en-US'))
|
|
370
|
-
// eslint-disable-next-line max-len
|
|
371
|
-
.toThrow(`ICU Message "{hello} {world}" contains a value reference ("world") that wasn't provided`);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
it('formats a message with plurals', () => {
|
|
375
|
-
const helloStr = str_(UIStrings.helloPlural, {itemCount: 3});
|
|
376
|
-
expect(helloStr).toBeDisplayString('hellos');
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
it('throws an error when a plural control value is missing', () => {
|
|
380
|
-
expect(_ => i18n.getFormatted(str_(UIStrings.helloPlural), 'en-US'))
|
|
381
|
-
// eslint-disable-next-line max-len
|
|
382
|
-
.toThrow(`ICU Message "{itemCount, plural, =1{1 hello} other{hellos}}" contains a value reference ("itemCount") that wasn't provided`);
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
it('formats a message with plurals and nested custom ICU', () => {
|
|
386
|
-
const helloStr = str_(UIStrings.helloPluralNestedICU, {itemCount: 3, in: 1875});
|
|
387
|
-
expect(helloStr).toBeDisplayString('hellos 2');
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
it('formats a message with plurals and nested custom ICU and nested plural', () => {
|
|
391
|
-
const helloStr = str_(UIStrings.helloPluralNestedPluralAndICU, {itemCount: 3,
|
|
392
|
-
innerItemCount: 1,
|
|
393
|
-
in: 1875});
|
|
394
|
-
expect(helloStr).toBeDisplayString('hellos 1 goodbye 2');
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
it('throws an error if a string value is used for a numeric placeholder', () => {
|
|
398
|
-
expect(_ => str_(UIStrings.helloTimeInMsWorld, {
|
|
399
|
-
timeInMs: 'string not a number',
|
|
400
|
-
}))
|
|
401
|
-
// eslint-disable-next-line max-len
|
|
402
|
-
.toThrow(`ICU Message "Hello {timeInMs, number, seconds} World" contains a numeric reference ("timeInMs") but provided value was not a number`);
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
it('throws an error if a value is provided that has no placeholder in the message', () => {
|
|
406
|
-
expect(_ => str_(UIStrings.helloTimeInMsWorld, {
|
|
407
|
-
timeInMs: 55,
|
|
408
|
-
sirNotAppearingInThisString: 66,
|
|
409
|
-
}))
|
|
410
|
-
// eslint-disable-next-line max-len
|
|
411
|
-
.toThrow(`Provided value "sirNotAppearingInThisString" does not match any placeholder in ICU message "Hello {timeInMs, number, seconds} World"`);
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
it('throws an error if a value is provided for a message with no placeholders', () => {
|
|
415
|
-
expect(_ => str_(UIStrings.helloWorld, {
|
|
416
|
-
extraCreditValue: 100,
|
|
417
|
-
}))
|
|
418
|
-
// eslint-disable-next-line max-len
|
|
419
|
-
.toThrow(`Provided value "extraCreditValue" does not match any placeholder in ICU message "Hello World"`);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it('formats correctly with NaN and Infinity numeric values', () => {
|
|
423
|
-
const helloInfinityStr = str_(UIStrings.helloBytesWorld, {in: Infinity});
|
|
424
|
-
expect(helloInfinityStr).toBeDisplayString('Hello ∞ World');
|
|
425
|
-
|
|
426
|
-
const helloNaNStr = str_(UIStrings.helloBytesWorld, {in: NaN});
|
|
427
|
-
expect(helloNaNStr).toBeDisplayString('Hello NaN World');
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license Copyright 2018 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
-
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import assert from 'assert/strict';
|
|
8
|
-
|
|
9
|
-
import {locales} from '../../localization/locales.js';
|
|
10
|
-
|
|
11
|
-
describe('locales', () => {
|
|
12
|
-
it('has only canonical (or expected-deprecated) language tags', () => {
|
|
13
|
-
// Map of deprecated codes to their canonical version. Depending on the ICU
|
|
14
|
-
// version used to run Lighthouse/this test, these *may* come back as their
|
|
15
|
-
// substitute, not themselves.
|
|
16
|
-
const deprecatedCodes = {
|
|
17
|
-
in: 'id',
|
|
18
|
-
iw: 'he',
|
|
19
|
-
mo: 'ro',
|
|
20
|
-
tl: 'fil',
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
for (const locale of Object.keys(locales)) {
|
|
24
|
-
const canonicalLocale = Intl.getCanonicalLocales(locale)[0];
|
|
25
|
-
const substitute = deprecatedCodes[locale];
|
|
26
|
-
assert.ok(locale === canonicalLocale || substitute === canonicalLocale,
|
|
27
|
-
`locale code '${locale}' not canonical ('${canonicalLocale}' found instead)`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Deprecation subsitutes should be removed from the test if no longer used.
|
|
31
|
-
for (const locale of Object.keys(deprecatedCodes)) {
|
|
32
|
-
assert.ok(locales[locale], `${locale} substitute should be removed from test`);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('has a base language prefix fallback for all supported languages', () => {
|
|
37
|
-
for (const locale of Object.keys(locales)) {
|
|
38
|
-
const basePrefix = locale.split('-')[0];
|
|
39
|
-
// The internet sez there is no canonical Chinese, so we exclude that one.
|
|
40
|
-
if (basePrefix !== 'zh') {
|
|
41
|
-
assert.ok(locales[basePrefix], `${locale} is missing a base fallback`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license Copyright 2019 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
-
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {swapLocale} from '../../localization/swap-locale.js';
|
|
8
|
-
import {readJson} from '../../../core/test/test-utils.js';
|
|
9
|
-
|
|
10
|
-
const lhr = readJson('core/test/results/sample_v2.json');
|
|
11
|
-
|
|
12
|
-
describe('swap-locale', () => {
|
|
13
|
-
it('does not mutate the original lhr', () => {
|
|
14
|
-
/** @type {LH.Result} */
|
|
15
|
-
const lhrClone = JSON.parse(JSON.stringify(lhr));
|
|
16
|
-
|
|
17
|
-
const lhrPt = swapLocale(lhr, 'pt').lhr;
|
|
18
|
-
expect(lhrPt).not.toStrictEqual(lhr);
|
|
19
|
-
expect(lhr).toStrictEqual(lhrClone);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('can change golden LHR english strings into german', () => {
|
|
23
|
-
/** @type {LH.Result} */
|
|
24
|
-
const lhrEn = JSON.parse(JSON.stringify(lhr));
|
|
25
|
-
const lhrDe = swapLocale(lhrEn, 'de').lhr;
|
|
26
|
-
|
|
27
|
-
// Basic replacement
|
|
28
|
-
expect(lhrEn.audits.plugins.title).toEqual('Document avoids plugins');
|
|
29
|
-
expect(lhrDe.audits.plugins.title).toEqual('Dokument verwendet keine Plug-ins');
|
|
30
|
-
|
|
31
|
-
// With ICU string argument values
|
|
32
|
-
expect(lhrEn.audits['dom-size'].displayValue).toMatchInlineSnapshot(`"153 elements"`);
|
|
33
|
-
/* eslint-disable no-irregular-whitespace */
|
|
34
|
-
expect(lhrDe.audits['dom-size'].displayValue).toMatchInlineSnapshot(`"153 Elemente"`);
|
|
35
|
-
|
|
36
|
-
// Renderer formatted strings
|
|
37
|
-
expect(lhrEn.i18n.rendererFormattedStrings.labDataTitle).toEqual('Lab Data');
|
|
38
|
-
expect(lhrDe.i18n.rendererFormattedStrings.labDataTitle).toEqual('Labdaten');
|
|
39
|
-
|
|
40
|
-
// Formatted numbers in placeholders.
|
|
41
|
-
expect(lhrEn.audits['mainthread-work-breakdown'].displayValue).
|
|
42
|
-
toMatchInlineSnapshot(`"2.2 s"`);
|
|
43
|
-
expect(lhrDe.audits['mainthread-work-breakdown'].displayValue).
|
|
44
|
-
toMatchInlineSnapshot(`"2,2 s"`);
|
|
45
|
-
/* eslint-enable no-irregular-whitespace */
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('can roundtrip back to english correctly', () => {
|
|
49
|
-
/** @type {LH.Result} */
|
|
50
|
-
const lhrEn = JSON.parse(JSON.stringify(lhr));
|
|
51
|
-
|
|
52
|
-
// via Spanish
|
|
53
|
-
const lhrEnEsRT = swapLocale(swapLocale(lhrEn, 'es').lhr, 'en-US').lhr;
|
|
54
|
-
expect(lhrEnEsRT).toEqual(lhrEn);
|
|
55
|
-
|
|
56
|
-
// via Arabic
|
|
57
|
-
const lhrEnArRT = swapLocale(swapLocale(lhrEn, 'ar').lhr, 'en-US').lhr;
|
|
58
|
-
expect(lhrEnArRT).toEqual(lhrEn);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('leaves alone messages where there is no translation available', () => {
|
|
62
|
-
const miniLHR = {
|
|
63
|
-
audits: {
|
|
64
|
-
redirects: {
|
|
65
|
-
id: 'redirects',
|
|
66
|
-
title: 'Avoid multiple page redirects',
|
|
67
|
-
doesntExist: 'A string that does not have localized versions',
|
|
68
|
-
},
|
|
69
|
-
fakeaudit: {
|
|
70
|
-
id: 'fakeaudit',
|
|
71
|
-
title: 'An audit without translations',
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
configSettings: {
|
|
75
|
-
locale: 'en-US',
|
|
76
|
-
},
|
|
77
|
-
i18n: {
|
|
78
|
-
icuMessagePaths: {
|
|
79
|
-
'core/audits/redirects.js | title': ['audits.redirects.title'],
|
|
80
|
-
// File that exists, but `doesntExist` message within it does not.
|
|
81
|
-
'core/audits/redirects.js | doesntExist': ['audits.redirects.doesntExist'],
|
|
82
|
-
// File and message which do not exist.
|
|
83
|
-
'core/audits/fakeaudit.js | title': ['audits.fakeaudit.title'],
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
const {missingIcuMessageIds} = swapLocale(miniLHR, 'es');
|
|
88
|
-
|
|
89
|
-
// Updated strings are not found, so these remain in the original language
|
|
90
|
-
expect(missingIcuMessageIds).toMatchInlineSnapshot(`
|
|
91
|
-
Array [
|
|
92
|
-
"core/audits/redirects.js | doesntExist",
|
|
93
|
-
"core/audits/fakeaudit.js | title",
|
|
94
|
-
]
|
|
95
|
-
`);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('does not change properties that are not strings', () => {
|
|
99
|
-
// Unlikely, but possible e.g. if an audit details changed shape over LH versions.
|
|
100
|
-
const miniLhr = {
|
|
101
|
-
audits: {
|
|
102
|
-
redirects: {
|
|
103
|
-
id: 'redirects',
|
|
104
|
-
title: 'Avoid multiple page redirects',
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
configSettings: {
|
|
108
|
-
locale: 'en-US',
|
|
109
|
-
},
|
|
110
|
-
i18n: {
|
|
111
|
-
icuMessagePaths: {
|
|
112
|
-
// Points to audit object, not string.
|
|
113
|
-
'core/audits/redirects.js | title': ['audits.redirects'],
|
|
114
|
-
// Path does not point to anything in LHR.
|
|
115
|
-
'core/audits/redirects.js | description': ['gatherers..X'],
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
const testLocale = 'ru';
|
|
120
|
-
const {lhr} = swapLocale(miniLhr, testLocale);
|
|
121
|
-
|
|
122
|
-
// LHR remains unchanged except for locale and injected `rendererFormattedStrings`.
|
|
123
|
-
miniLhr.configSettings.locale = testLocale;
|
|
124
|
-
miniLhr.i18n.rendererFormattedStrings = expect.any(Object);
|
|
125
|
-
expect(lhr).toEqual(miniLhr);
|
|
126
|
-
});
|
|
127
|
-
});
|