Skip to content

Commit

Permalink
core(fr): api for constructing flow result (#13034)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamraine authored Sep 22, 2021
1 parent 15c4300 commit 9dbaf73
Show file tree
Hide file tree
Showing 12 changed files with 21,688 additions and 21,973 deletions.
9 changes: 4 additions & 5 deletions flow-report/src/sidebar/flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import {FunctionComponent} from 'preact';

import {FlowSegment} from '../common';
import {classNames, useCurrentLhr, useDerivedStepNames, useFlowResult} from '../util';
import {classNames, useCurrentLhr, useFlowResult} from '../util';
import {Separator} from '../common';

const SidebarFlowStep: FunctionComponent<{
Expand Down Expand Up @@ -42,13 +42,12 @@ const SidebarFlowSeparator: FunctionComponent = () => {
export const SidebarFlow: FunctionComponent = () => {
const flowResult = useFlowResult();
const currentLhr = useCurrentLhr();
const stepNames = useDerivedStepNames();

return (
<div className="SidebarFlow">
{
flowResult.lhrs.map((lhr, index) => {
const stepName = stepNames[index];
flowResult.steps.map((step, index) => {
const {lhr, name} = step;
const url = new URL(location.href);
url.hash = `#index=${index}`;
return <>
Expand All @@ -61,7 +60,7 @@ export const SidebarFlow: FunctionComponent = () => {
key={lhr.fetchTime}
mode={lhr.gatherMode}
href={url.href}
label={stepName}
label={name}
isCurrent={index === (currentLhr && currentLhr.index)}
/>
</>;
Expand Down
2 changes: 1 addition & 1 deletion flow-report/src/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const SidebarHeader: FunctionComponent<{title: string, date: string}> = (

export const Sidebar: FunctionComponent = () => {
const flowResult = useFlowResult();
const firstLhr = flowResult.lhrs[0];
const firstLhr = flowResult.steps[0].lhr;
return (
<div className="Sidebar">
<SidebarHeader title="Lighthouse User Flow Report" date={firstLhr.fetchTime}/>
Expand Down
15 changes: 7 additions & 8 deletions flow-report/src/summary/summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {FunctionComponent} from 'preact';
import {useMemo} from 'preact/hooks';

import {FlowSegment, Separator} from '../common';
import {getScreenDimensions, getScreenshot, useDerivedStepNames, useFlowResult} from '../util';
import {getScreenDimensions, getScreenshot, useFlowResult} from '../util';
import {Util} from '../../../report/renderer/util';
import {CategoryScore} from '../wrappers/category-score';

Expand Down Expand Up @@ -113,15 +113,14 @@ export const SummaryFlowStep: FunctionComponent<{
*/
const SummaryFlow: FunctionComponent = () => {
const flowResult = useFlowResult();
const stepNames = useDerivedStepNames();
return (
<div className="SummaryFlow">
{
flowResult.lhrs.map((lhr, index) =>
flowResult.steps.map((step, index) =>
<SummaryFlowStep
key={lhr.fetchTime}
lhr={lhr}
label={stepNames[index]}
key={step.lhr.fetchTime}
lhr={step.lhr}
label={step.name}
hashIndex={index}
/>
)
Expand All @@ -136,8 +135,8 @@ export const SummaryHeader: FunctionComponent = () => {
let numNavigation = 0;
let numTimespan = 0;
let numSnapshot = 0;
for (const lhr of flowResult.lhrs) {
switch (lhr.gatherMode) {
for (const step of flowResult.steps) {
switch (step.lhr.gatherMode) {
case 'navigation':
numNavigation++;
break;
Expand Down
67 changes: 5 additions & 62 deletions flow-report/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,6 @@ function getHashParam(param: string): string|null {
return params.get(param);
}

function shortenUrl(longUrl: string) {
const url = new URL(longUrl);
return `${url.hostname}${url.pathname}`;
}

/**
* The step label should be enumerated if there is another report of the same gather mode in the same section.
* Navigation reports will never be enumerated.
*/
function shouldEnumerate(flowResult: LH.FlowResult, index: number) {
const {lhrs} = flowResult;
if (lhrs[index].gatherMode === 'navigation') return false;

for (let i = index + 1; lhrs[i] && lhrs[i].gatherMode !== 'navigation'; ++i) {
if (lhrs[i].gatherMode === lhrs[index].gatherMode) {
return true;
}
}
for (let i = index - 1; lhrs[i] && lhrs[i].gatherMode !== 'navigation'; --i) {
if (lhrs[i].gatherMode === lhrs[index].gatherMode) {
return true;
}
}
return false;
}


export function classNames(...args: Array<string|undefined|Record<string, boolean>>): string {
const classes = [];
for (const arg of args) {
Expand Down Expand Up @@ -83,7 +56,7 @@ export function useFlowResult(): LH.FlowResult {

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

export function useHashParam(param: string) {
Expand Down Expand Up @@ -118,42 +91,12 @@ export function useCurrentLhr(): {value: LH.Result, index: number}|null {
return null;
}

const value = flowResult.lhrs[index];
if (!value) {
console.warn(`No LHR at index ${index}`);
const step = flowResult.steps[index];
if (!step) {
console.warn(`No flow step at index ${index}`);
return null;
}

return {value, index};
return {value: step.lhr, index};
}, [indexString, flowResult]);
}

export function useDerivedStepNames() {
const flowResult = useFlowResult();

return useMemo(() => {
let numTimespan = 1;
let numSnapshot = 1;

return flowResult.lhrs.map((lhr, index) => {
const shortUrl = shortenUrl(lhr.finalUrl);

switch (lhr.gatherMode) {
case 'navigation':
numTimespan = 1;
numSnapshot = 1;
return `Navigation report (${shortUrl})`;
case 'timespan':
if (shouldEnumerate(flowResult, index)) {
return `Timespan report ${numTimespan++} (${shortUrl})`;
}
return `Timespan report (${shortUrl})`;
case 'snapshot':
if (shouldEnumerate(flowResult, index)) {
return `Snapshot report ${numSnapshot++} (${shortUrl})`;
}
return `Snapshot report (${shortUrl})`;
}
});
}, [flowResult]);
}
4 changes: 2 additions & 2 deletions flow-report/test/sidebar/flow-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ describe('SidebarFlow', () => {
const root = render(<SidebarFlow/>, {wrapper});

const navigation = root.getByText('Navigation report (www.mikescerealshack.co/)');
const timespan = root.getByText('Timespan report (www.mikescerealshack.co/search)');
const snapshot = root.getByText('Snapshot report (www.mikescerealshack.co/search)');
const timespan = root.getByText('Search input');
const snapshot = root.getByText('Search results');
const navigation2 = root.getByText('Navigation report (www.mikescerealshack.co/corrections)');

const links = root.getAllByRole('link') as HTMLAnchorElement[];
Expand Down
15 changes: 8 additions & 7 deletions flow-report/test/summary/summary-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('SummaryHeader', () => {
describe('SummaryFlowStep', () => {
it('renders navigation step', async () => {
const root = render(<SummaryFlowStep
lhr={flowResult.lhrs[0]}
lhr={flowResult.steps[0].lhr}
label="Navigation (1)"
hashIndex={0}
/>, {wrapper});
Expand All @@ -77,7 +77,7 @@ describe('SummaryFlowStep', () => {

it('renders timespan step', async () => {
const root = render(<SummaryFlowStep
lhr={flowResult.lhrs[1]}
lhr={flowResult.steps[1].lhr}
label="Timespan (1)"
hashIndex={1}
/>, {wrapper});
Expand All @@ -89,23 +89,24 @@ describe('SummaryFlowStep', () => {
const screenshot = root.getByTestId('SummaryFlowStep__screenshot') as HTMLImageElement;
expect(screenshot.src).toBeFalsy();

expect(root.getByTestId('SummaryCategory__null'));
// Accessibility and SEO are missing in timespan.
const nullCategories = root.getAllByTestId('SummaryCategory__null');
expect(nullCategories).toHaveLength(2);

const gauges = root.getAllByTestId('CategoryScore');
expect(gauges).toHaveLength(3);
expect(gauges).toHaveLength(2);

const links = root.getAllByRole('link') as HTMLAnchorElement[];
expect(links.map(a => a.href)).toEqual([
'file:///Users/example/report.html/#index=1',
'file:///Users/example/report.html/#index=1&anchor=performance',
// Accessibility is missing in timespan.
'file:///Users/example/report.html/#index=1&anchor=best-practices',
'file:///Users/example/report.html/#index=1&anchor=seo',
]);
});

it('renders snapshot step', async () => {
const root = render(<SummaryFlowStep
lhr={flowResult.lhrs[2]}
lhr={flowResult.steps[2].lhr}
label="Snapshot (1)"
hashIndex={2}
/>, {wrapper});
Expand Down
45 changes: 11 additions & 34 deletions flow-report/test/util-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import fs from 'fs';
import {dirname} from 'path';
import {fileURLToPath} from 'url';

import {jest} from '@jest/globals';
import {renderHook} from '@testing-library/preact-hooks';
import {FunctionComponent} from 'preact';
import {act} from 'preact/test-utils';

import {FlowResultContext, useCurrentLhr, useDerivedStepNames} from '../src/util';
import {FlowResultContext, useCurrentLhr} from '../src/util';

const flowResult: LH.FlowResult = JSON.parse(
fs.readFileSync(
Expand All @@ -25,6 +26,7 @@ const flowResult: LH.FlowResult = JSON.parse(
let wrapper: FunctionComponent;

beforeEach(() => {
global.console.warn = jest.fn();
wrapper = ({children}) => (
<FlowResultContext.Provider value={flowResult}>{children}</FlowResultContext.Provider>
);
Expand All @@ -34,9 +36,10 @@ describe('useCurrentLhr', () => {
it('gets current lhr index from url hash', () => {
global.location.hash = '#index=1';
const {result} = renderHook(() => useCurrentLhr(), {wrapper});
expect(console.warn).not.toHaveBeenCalled();
expect(result.current).toEqual({
index: 1,
value: flowResult.lhrs[1],
value: flowResult.steps[1].lhr,
});
});

Expand All @@ -46,64 +49,38 @@ describe('useCurrentLhr', () => {

expect(render.result.current).toEqual({
index: 1,
value: flowResult.lhrs[1],
value: flowResult.steps[1].lhr,
});

await act(() => {
global.location.hash = '#index=2';
});
await render.waitForNextUpdate();

expect(console.warn).not.toHaveBeenCalled();
expect(render.result.current).toEqual({
index: 2,
value: flowResult.lhrs[2],
value: flowResult.steps[2].lhr,
});
});

it('return null if lhr index is unset', () => {
const {result} = renderHook(() => useCurrentLhr(), {wrapper});
expect(console.warn).not.toHaveBeenCalled();
expect(result.current).toBeNull();
});

it('return null if lhr index is out of bounds', () => {
global.location.hash = '#index=5';
const {result} = renderHook(() => useCurrentLhr(), {wrapper});
expect(console.warn).toHaveBeenCalled();
expect(result.current).toBeNull();
});

it('returns null for invalid value', () => {
global.location.hash = '#index=OHNO';
const {result} = renderHook(() => useCurrentLhr(), {wrapper});
expect(console.warn).toHaveBeenCalled();
expect(result.current).toBeNull();
});
});

describe('useDerivedStepNames', () => {
it('counts up for each mode', () => {
const {result} = renderHook(() => useDerivedStepNames(), {wrapper});
expect(result.current).toEqual([
'Navigation report (www.mikescerealshack.co/)',
'Timespan report (www.mikescerealshack.co/search)',
'Snapshot report (www.mikescerealshack.co/search)',
'Navigation report (www.mikescerealshack.co/corrections)',
]);
});

it('enumerates if multiple in same group', () => {
const lhrs = flowResult.lhrs;
lhrs[3] = lhrs[2];
const newFlowResult = {lhrs};
const wrapper: FunctionComponent = ({children}) => (
<FlowResultContext.Provider value={newFlowResult}>{children}</FlowResultContext.Provider>
);

const {result} = renderHook(() => useDerivedStepNames(), {wrapper});

expect(result.current).toEqual([
'Navigation report (www.mikescerealshack.co/)',
'Timespan report (www.mikescerealshack.co/search)',
'Snapshot report 1 (www.mikescerealshack.co/search)',
'Snapshot report 2 (www.mikescerealshack.co/search)',
]);
});
});
Loading

0 comments on commit 9dbaf73

Please sign in to comment.