Skip to content

Commit

Permalink
report(flow): import lhr strings (#13215)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamraine authored Oct 18, 2021
1 parent 68013fd commit bbb2d67
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 44 deletions.
4 changes: 2 additions & 2 deletions flow-report/src/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {FunctionComponent} from 'preact';

import {Util} from '../../report/renderer/util';
import {FlowStepIcon, FlowStepThumbnail} from './common';
import {useUIStrings} from './i18n/i18n';
import {useLocalizedStrings} from './i18n/i18n';
import {getModeDescription, useFlowResult} from './util';

const SIDE_THUMBNAIL_HEIGHT = 80;
Expand Down Expand Up @@ -39,7 +39,7 @@ export const Header: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
const prevStep = flowResult.steps[index - 1];
const nextStep = flowResult.steps[index + 1];

const strings = useUIStrings();
const strings = useLocalizedStrings();
const modeDescription = getModeDescription(step.lhr.gatherMode, strings);

return (
Expand Down
6 changes: 3 additions & 3 deletions flow-report/src/help-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import {FunctionComponent, JSX} from 'preact';

import {useUIStrings} from './i18n/i18n';
import {useLocalizedStrings} from './i18n/i18n';
import {CloseIcon, NavigationIcon, SnapshotIcon, TimespanIcon} from './icons';

const HelpDialogColumn: FunctionComponent<{
Expand All @@ -18,7 +18,7 @@ const HelpDialogColumn: FunctionComponent<{
useCases: string[];
availableCategories: string[];
}> = (props) => {
const strings = useUIStrings();
const strings = useLocalizedStrings();

return (
<div className="HelpDialogColumn">
Expand Down Expand Up @@ -56,7 +56,7 @@ const HelpDialogColumn: FunctionComponent<{
export const HelpDialog: FunctionComponent<{onClose: () => void}> = ({
onClose,
}) => {
const strings = useUIStrings();
const strings = useLocalizedStrings();

return (
<div className="HelpDialog">
Expand Down
57 changes: 43 additions & 14 deletions flow-report/src/i18n/i18n.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,66 @@ import {useContext, useMemo} from 'preact/hooks';
import {formatMessage} from '../../../shared/localization/format';
import {I18n} from '../../../report/renderer/i18n';
import {UIStrings} from './ui-strings';
import {useLocale} from '../util';
import {useFlowResult} from '../util';
import strings from './localized-strings';
import {Util} from '../../../report/renderer/util';

const I18nContext = createContext<I18n<typeof UIStrings>|undefined>(undefined);
const I18nContext = createContext(new I18n('en-US', {...Util.UIStrings, ...UIStrings}));

function useLhrLocale() {
const flowResult = useFlowResult();
const firstLhr = flowResult.steps[0].lhr;
const locale = firstLhr.configSettings.locale;

if (flowResult.steps.some(step => step.lhr.configSettings.locale !== locale)) {
console.warn('LHRs have inconsistent locales');
}

return {
locale,
lhrStrings: firstLhr.i18n.rendererFormattedStrings,
};
}

export function useI18n() {
const i18n = useContext(I18nContext);
if (!i18n) throw Error('i18n was not initialized');
return i18n;
return useContext(I18nContext);
}

export function useUIStrings() {
export function useLocalizedStrings() {
const i18n = useI18n();
return i18n.strings;
}

export function useStringFormatter() {
const locale = useLocale();
const {locale} = useLhrLocale();
return (str: string, values?: Record<string, string|number>) => {
return formatMessage(str, values, locale);
};
}

export const I18nProvider: FunctionComponent = ({children}) => {
const locale = useLocale();
const i18n = useMemo(() => new I18n(locale, {
// Set missing renderer strings to default (english) values.
...UIStrings,
// `strings` is generated in build/build-report.js
...strings[locale],
}), [locale]);
const {locale, lhrStrings} = useLhrLocale();

const i18n = useMemo(() => {
const i18n = new I18n(locale, {
// Set any missing lhr strings to default (english) values.
...Util.UIStrings,
// Preload with strings from the first lhr.
// Used for legacy report components imported into the flow report.
...lhrStrings,
// Set any missing flow strings to default (english) values.
...UIStrings,
// `strings` is generated in build/build-report.js
...strings[locale],
});

// Initialize renderer util i18n for strings rendered in wrapped components.
// TODO: Don't attach global i18n to `Util`.
// @ts-ignore TS reports as read-only.
Util.i18n = i18n;

return i18n;
}, [locale, lhrStrings]);

return (
<I18nContext.Provider value={i18n}>
Expand Down
14 changes: 8 additions & 6 deletions flow-report/src/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
import {FunctionComponent} from 'preact';

import {Separator} from '../common';
import {useI18n, useUIStrings} from '../i18n/i18n';
import {useI18n, useLocalizedStrings} from '../i18n/i18n';
import {CpuIcon, EnvIcon, SummaryIcon} from '../icons';
import {classNames, useCurrentLhr, useFlowResult} from '../util';
import {SidebarFlow} from './flow';

export const SidebarSummary: FunctionComponent = () => {
const currentLhr = useCurrentLhr();
const strings = useUIStrings();
const strings = useLocalizedStrings();

const url = new URL(location.href);
url.hash = '#';
Expand All @@ -33,19 +33,21 @@ export const SidebarSummary: FunctionComponent = () => {
};

const SidebarRuntimeSettings: FunctionComponent<{settings: LH.ConfigSettings}> = ({settings}) => {
const strings = useUIStrings();
const strings = useLocalizedStrings();

return (
<div className="SidebarRuntimeSettings">
<div className="SidebarRuntimeSettings__item">
<div className="SidebarRuntimeSettings__item" title={strings.runtimeSettingsDevice}>
<div className="SidebarRuntimeSettings__item--icon">
<EnvIcon/>
</div>
{
settings.formFactor === 'desktop' ? strings.desktop : strings.mobile
settings.formFactor === 'desktop' ?
strings.runtimeDesktopEmulation :
strings.runtimeMobileEmulation
}
</div>
<div className="SidebarRuntimeSettings__item">
<div className="SidebarRuntimeSettings__item" title={strings.runtimeSettingsCPUThrottling}>
<div className="SidebarRuntimeSettings__item--icon">
<CpuIcon/>
</div>
Expand Down
16 changes: 9 additions & 7 deletions flow-report/src/summary/category.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {FunctionComponent} from 'preact';
import {Util} from '../../../report/renderer/util';
import {Separator} from '../common';
import {CategoryScore} from '../wrappers/category-score';
import {useStringFormatter, useUIStrings} from '../i18n/i18n';
import {useI18n, useStringFormatter, useLocalizedStrings} from '../i18n/i18n';

import type {UIStringsType} from '../i18n/ui-strings';

Expand All @@ -34,7 +34,7 @@ export const SummaryTooltip: FunctionComponent<{
category: LH.ReportResult.Category,
gatherMode: LH.Result.GatherMode
}> = ({category, gatherMode}) => {
const strings = useUIStrings();
const strings = useLocalizedStrings();
const str_ = useStringFormatter();
const {
numPassed,
Expand All @@ -43,10 +43,12 @@ export const SummaryTooltip: FunctionComponent<{
totalWeight,
} = Util.calculateCategoryFraction(category);

const i18n = useI18n();
const displayAsFraction = Util.shouldDisplayAsFraction(gatherMode);
const rating = displayAsFraction ?
Util.calculateRating(numPassed / numPassableAudits) :
Util.calculateRating(category.score);
const score = displayAsFraction ?
numPassed / numPassableAudits :
category.score;
const rating = score === null ? 'error' : Util.calculateRating(score);

return (
<div className="SummaryTooltip">
Expand All @@ -61,9 +63,9 @@ export const SummaryTooltip: FunctionComponent<{
<div className={`SummaryTooltip__rating SummaryTooltip__rating--${rating}`}>
<span>{getCategoryRating(rating, strings)}</span>
{
!displayAsFraction && category.score && <>
!displayAsFraction && category.score !== null && <>
<span> · </span>
<span>{category.score * 100}</span>
<span>{i18n.formatNumber(category.score * 100)}</span>
</>
}
</div>
Expand Down
10 changes: 5 additions & 5 deletions flow-report/src/summary/summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import {FlowSegment, FlowStepThumbnail, Separator} from '../common';
import {getModeDescription, useFlowResult} from '../util';
import {Util} from '../../../report/renderer/util';
import {SummaryCategory} from './category';
import {useStringFormatter, useUIStrings} from '../i18n/i18n';
import {useStringFormatter, useLocalizedStrings} from '../i18n/i18n';

const DISPLAYED_CATEGORIES = ['performance', 'accessibility', 'best-practices', 'seo'];
const THUMBNAIL_WIDTH = 50;

const SummaryNavigationHeader: FunctionComponent<{lhr: LH.Result}> = ({lhr}) => {
const strings = useUIStrings();
const strings = useLocalizedStrings();

return (
<div className="SummaryNavigationHeader" data-testid="SummaryNavigationHeader">
Expand Down Expand Up @@ -50,7 +50,7 @@ export const SummaryFlowStep: FunctionComponent<{
hashIndex: number,
}> = ({lhr, label, hashIndex}) => {
const reportResult = useMemo(() => Util.prepareReportResult(lhr), [lhr]);
const strings = useUIStrings();
const strings = useLocalizedStrings();
const modeDescription = getModeDescription(lhr.gatherMode, strings);

return (
Expand Down Expand Up @@ -107,7 +107,7 @@ const SummaryFlow: FunctionComponent = () => {

export const SummaryHeader: FunctionComponent = () => {
const flowResult = useFlowResult();
const strings = useUIStrings();
const strings = useLocalizedStrings();
const str_ = useStringFormatter();

let numNavigation = 0;
Expand Down Expand Up @@ -151,7 +151,7 @@ const SummarySectionHeader: FunctionComponent = ({children}) => {
};

export const Summary: FunctionComponent = () => {
const strings = useUIStrings();
const strings = useLocalizedStrings();

return (
<div className="Summary" data-testid="Summary">
Expand Down
4 changes: 2 additions & 2 deletions flow-report/src/topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {useState} from 'preact/hooks';

import {HelpDialog} from './help-dialog';
import {getFilenamePrefix} from '../../report/generator/file-namer';
import {useUIStrings} from './i18n/i18n';
import {useLocalizedStrings} from './i18n/i18n';
import {HamburgerIcon} from './icons';
import {useFlowResult} from './util';
import {useReportRenderer} from './wrappers/report-renderer';
Expand Down Expand Up @@ -79,7 +79,7 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
({onMenuClick}) => {
const flowResult = useFlowResult();
const {dom} = useReportRenderer();
const strings = useUIStrings();
const strings = useLocalizedStrings();
const [showHelpDialog, setShowHelpDialog] = useState(false);

return (
Expand Down
5 changes: 0 additions & 5 deletions flow-report/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,6 @@ export function useFlowResult(): LH.FlowResult {
return flowResult;
}

export function useLocale(): LH.Locale {
const flowResult = useFlowResult();
return flowResult.steps[0].lhr.configSettings.locale;
}

export function useHashParam(param: string) {
const [paramValue, setParamValue] = useState(getHashParam(param));

Expand Down
1 change: 1 addition & 0 deletions flow-report/test/topbar-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const flowResult = {
steps: [{lhr: {
fetchTime: '2021-09-14T22:24:22.462Z',
configSettings: {locale: 'en-US'},
i18n: {rendererFormattedStrings: {}},
}}],
} as any;

Expand Down
88 changes: 88 additions & 0 deletions flow-report/test/wrappers/category-score-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
* 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
* 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.
*/

import {FunctionComponent} from 'preact';
import {render} from '@testing-library/preact';

import {CategoryScore} from '../../src/wrappers/category-score';
import {FlowResultContext} from '../../src/util';
import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
import {I18nProvider} from '../../src/i18n/i18n';
import {flowResult} from '../sample-flow';

let wrapper: FunctionComponent;

beforeEach(() => {
wrapper = ({children}) => (
<FlowResultContext.Provider value={flowResult}>
<ReportRendererProvider>
<I18nProvider>
{children}
</I18nProvider>
</ReportRendererProvider>
</FlowResultContext.Provider>
);
});

describe('CategoryScore', () => {
it('renders score gauge', () => {
const category: any = {
id: 'seo',
score: 0.95,
auditRefs: [],
};
const root = render(
<CategoryScore category={category} href="#seo" gatherMode="navigation"/>,
{wrapper}
);

const link = root.getByRole('link') as HTMLAnchorElement;

expect(link.href).toEqual('file:///Users/example/report.html/#seo');
expect(root.getByText('95')).toBeTruthy();
expect(root.baseElement.querySelector('.lh-gauge__label')).toBeFalsy();
});

it('renders error gauge', () => {
const category: any = {
id: 'seo',
score: null,
auditRefs: [],
};
const root = render(
<CategoryScore category={category} href="#seo" gatherMode="navigation"/>,
{wrapper}
);

const link = root.getByRole('link') as HTMLAnchorElement;

expect(link.href).toEqual('file:///Users/example/report.html/#seo');
expect(root.getByText('?')).toBeTruthy();
});

it('renders category fraction', () => {
const category: any = {
id: 'seo',
auditRefs: [
{weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}},
{weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}},
{weight: 1, result: {score: 0, scoreDisplayMode: 'binary'}},
{weight: 1, result: {score: 0, scoreDisplayMode: 'binary'}},
],
};
const root = render(
<CategoryScore category={category} href="#seo" gatherMode="timespan"/>,
{wrapper}
);

const link = root.getByRole('link') as HTMLAnchorElement;

expect(link.href).toEqual('file:///Users/example/report.html/#seo');
expect(root.getByText('2/4')).toBeTruthy();
expect(root.baseElement.querySelector('.lh-fraction__label')).toBeFalsy();
expect(root.baseElement.querySelector('.lh-fraction__background')).toBeFalsy();
});
});
2 changes: 2 additions & 0 deletions lighthouse-core/util-commonjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ class Util {

/**
* Convert a score to a rating label.
* TODO: Return `'error'` for `score === null && !scoreDisplayMode`.
*
* @param {number|null} score
* @param {string=} scoreDisplayMode
* @return {string}
Expand Down
Loading

0 comments on commit bbb2d67

Please sign in to comment.