diff --git a/packages/compare/src/compare.ts b/packages/compare/src/compare.ts index 4be766c7..02601925 100644 --- a/packages/compare/src/compare.ts +++ b/packages/compare/src/compare.ts @@ -163,7 +163,7 @@ function compareResults(current: MeasureResults, baseline: MeasureResults | null .filter((item) => Math.abs(item.countDiff) > COUNT_DIFF_THRESHOLD) .sort((a, b) => b.countDiff - a.countDiff); const renderIssues = withCurrent.filter( - (item) => item.current.initialUpdateCount || item.current.redundantUpdates?.length + (item) => item.current.issues?.initialUpdateCount || item.current.issues?.redundantUpdates?.length ); added.sort((a, b) => a.name.localeCompare(b.name)); removed.sort((a, b) => a.name.localeCompare(b.name)); diff --git a/packages/compare/src/output/console.ts b/packages/compare/src/output/console.ts index fe608122..a4ee3387 100644 --- a/packages/compare/src/output/console.ts +++ b/packages/compare/src/output/console.ts @@ -64,12 +64,15 @@ function printRegularLine(entry: CompareEntry) { function printRenderIssuesLine(entry: CompareEntry | AddedEntry) { const issues = []; - if (entry.current.initialUpdateCount !== 0) { - issues.push(formatInitialUpdates(entry.current.initialUpdateCount)); + + const initialUpdateCount = entry.current.issues?.initialUpdateCount; + if (initialUpdateCount) { + issues.push(formatInitialUpdates(initialUpdateCount)); } - if (entry.current.redundantUpdates?.length !== 0) { - issues.push(formatRedundantUpdates(entry.current.redundantUpdates)); + const redundantUpdates = entry.current.issues?.redundantUpdates; + if (redundantUpdates?.length) { + issues.push(formatRedundantUpdates(redundantUpdates)); } logger.log(` - ${entry.name}: ${issues.join(' | ')}`); @@ -89,16 +92,14 @@ function printRemovedLine(entry: RemovedEntry) { ); } -export function formatInitialUpdates(count: number | undefined) { - if (count == null) return '?'; +export function formatInitialUpdates(count: number) { if (count === 0) return '-'; if (count === 1) return '1 initial update 🔴'; return `${count} initial updates 🔴`; } -export function formatRedundantUpdates(redundantUpdates: number[] | undefined) { - if (redundantUpdates == null) return '?'; +export function formatRedundantUpdates(redundantUpdates: number[]) { if (redundantUpdates.length === 0) return '-'; if (redundantUpdates.length === 1) return `1 redundant update (${redundantUpdates.join(', ')}) 🔴`; diff --git a/packages/compare/src/output/markdown.ts b/packages/compare/src/output/markdown.ts index e47d3e65..f2be3be5 100644 --- a/packages/compare/src/output/markdown.ts +++ b/packages/compare/src/output/markdown.ts @@ -186,8 +186,8 @@ function buildRedundantRendersTable(entries: Array) { const tableHeader = ['Name', 'Initial Updates', 'Redundant Updates'] as const; const rows = entries.map((entry) => [ entry.name, - formatInitialUpdates(entry.current.initialUpdateCount), - formatRedundantUpdates(entry.current.redundantUpdates), + formatInitialUpdates(entry.current.issues?.initialUpdateCount), + formatRedundantUpdates(entry.current.issues?.redundantUpdates), ]); return markdownTable([tableHeader, ...rows]); diff --git a/packages/compare/src/type-schemas.ts b/packages/compare/src/type-schemas.ts index 98eff5eb..b0dead0e 100644 --- a/packages/compare/src/type-schemas.ts +++ b/packages/compare/src/type-schemas.ts @@ -44,6 +44,10 @@ export const MeasureEntryScheme = z.object({ /** Array of measured render/execution counts for each run. */ counts: z.array(z.number()), - initialUpdateCount: z.number().optional(), - redundantUpdates: z.array(z.number()).optional(), + issues: z.optional( + z.object({ + initialUpdateCount: z.number().optional(), + redundantUpdates: z.array(z.number()).optional(), + }) + ), }); diff --git a/packages/measure/src/__tests__/measure-renders.test.tsx b/packages/measure/src/__tests__/measure-renders.test.tsx index bba272ad..e50663a5 100644 --- a/packages/measure/src/__tests__/measure-renders.test.tsx +++ b/packages/measure/src/__tests__/measure-renders.test.tsx @@ -92,8 +92,8 @@ test('measureRenders correctly measures regular renders', async () => { }; const results = await measureRenders(, { scenario, writeFile: false }); - expect(results.initialUpdateCount).toBe(0); - expect(results.redundantUpdates).toEqual([]); + expect(results.issues.initialUpdateCount).toBe(0); + expect(results.issues.redundantUpdates).toEqual([]); }); const InitialUpdates = ({ updateCount }: { updateCount: number }) => { @@ -114,17 +114,17 @@ const InitialUpdates = ({ updateCount }: { updateCount: number }) => { test('measureRenders detects redundant initial renders', async () => { const results = await measureRenders(, { writeFile: false }); - expect(results.initialUpdateCount).toBe(1); - expect(results.redundantUpdates).toEqual([]); + expect(results.issues.initialUpdateCount).toBe(1); + expect(results.issues.redundantUpdates).toEqual([]); }); test('measureRenders detects multiple redundant initial renders', async () => { const results = await measureRenders(, { writeFile: false }); - expect(results.initialUpdateCount).toBe(5); - expect(results.redundantUpdates).toEqual([]); + expect(results.issues.initialUpdateCount).toBe(5); + expect(results.issues.redundantUpdates).toEqual([]); }); -const RedundantUpdates = () => { +const RenderIssues = () => { const [count, setCount] = React.useState(0); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_, forceRender] = React.useState(0); @@ -148,9 +148,9 @@ test('measureRenders detects redundant updates', async () => { await fireEvent.press(screen.getByText('Redundant')); }; - const results = await measureRenders(, { scenario, writeFile: false }); - expect(results.redundantUpdates).toEqual([1]); - expect(results.initialUpdateCount).toBe(0); + const results = await measureRenders(, { scenario, writeFile: false }); + expect(results.issues.redundantUpdates).toEqual([1]); + expect(results.issues.initialUpdateCount).toBe(0); }); test('measureRenders detects multiple redundant updates', async () => { @@ -160,9 +160,9 @@ test('measureRenders detects multiple redundant updates', async () => { await fireEvent.press(screen.getByText('Redundant')); }; - const results = await measureRenders(, { scenario, writeFile: false }); - expect(results.redundantUpdates).toEqual([1, 3]); - expect(results.initialUpdateCount).toBe(0); + const results = await measureRenders(, { scenario, writeFile: false }); + expect(results.issues.redundantUpdates).toEqual([1, 3]); + expect(results.issues.initialUpdateCount).toBe(0); }); const AsyncMacroTaskEffect = () => { @@ -181,8 +181,8 @@ const AsyncMacroTaskEffect = () => { test('ignores async macro-tasks effect', async () => { const results = await measureRenders(, { writeFile: false }); - expect(results.initialUpdateCount).toBe(0); - expect(results.redundantUpdates).toEqual([]); + expect(results.issues.initialUpdateCount).toBe(0); + expect(results.issues.redundantUpdates).toEqual([]); }); const AsyncMicrotaskEffect = () => { @@ -206,8 +206,8 @@ const AsyncMicrotaskEffect = () => { test('ignores async micro-tasks effect', async () => { const results = await measureRenders(, { writeFile: false }); - expect(results.initialUpdateCount).toBe(0); - expect(results.redundantUpdates).toEqual([]); + expect(results.issues.initialUpdateCount).toBe(0); + expect(results.issues.redundantUpdates).toEqual([]); }); function Wrapper({ children }: React.PropsWithChildren<{}>) { diff --git a/packages/measure/src/measure-renders.tsx b/packages/measure/src/measure-renders.tsx index b3cb4b1a..9a3a0ad3 100644 --- a/packages/measure/src/measure-renders.tsx +++ b/packages/measure/src/measure-renders.tsx @@ -126,8 +126,10 @@ async function measureRendersInternal( return { ...processRunResults(runResults, warmupRuns), - initialUpdateCount: initialRenderCount - 1, - redundantUpdates: detectRedundantUpdates(renderJsonTrees, initialRenderCount), + issues: { + initialUpdateCount: initialRenderCount - 1, + redundantUpdates: detectRedundantUpdates(renderJsonTrees, initialRenderCount), + }, }; } diff --git a/packages/measure/src/types.ts b/packages/measure/src/types.ts index c05ec9d9..a01e6e8e 100644 --- a/packages/measure/src/types.ts +++ b/packages/measure/src/types.ts @@ -31,6 +31,10 @@ export interface MeasureResults { } export interface MeasureRendersResults extends MeasureResults { + issues: RenderIssues; +} + +export interface RenderIssues { /** * Update renders (re-renders) that happened immediately after component was created * e.g., synchronous `useEffects` containing `setState`.