diff --git a/src/components/ErrorOverview.tsx b/src/components/ErrorOverview.tsx index 5b597872f..1349f8837 100644 --- a/src/components/ErrorOverview.tsx +++ b/src/components/ErrorOverview.tsx @@ -27,6 +27,7 @@ export default function ErrorOverview({error}: Props) { const filePath = cleanupPath(origin?.file); let excerpt: CodeExcerpt[] | undefined; let lineWidth = 0; + const stackLineCounts = new Map(); if (filePath && origin?.line && fs.existsSync(filePath)) { const sourceCode = fs.readFileSync(filePath, 'utf8'); @@ -96,11 +97,16 @@ export default function ErrorOverview({error}: Props) { .slice(1) .map(line => { const parsedLine = stackUtils.parseLine(line); + const lineCount = stackLineCounts.get(line) ?? 0; + stackLineCounts.set(line, lineCount + 1); + const key = `${line}-${lineCount}`; - // If the line from the stack cannot be parsed, we print out the unparsed line. - if (!parsedLine) { + // If the line from the stack cannot be parsed, or parsed into an incomplete + // frame without source location data (for example, "at native"), we print + // out the unparsed line. + if (!parsedLine?.file || !parsedLine.line || !parsedLine.column) { return ( - + - {line} @@ -111,7 +117,7 @@ export default function ErrorOverview({error}: Props) { } return ( - + - {parsedLine.function} diff --git a/test/error-overview.tsx b/test/error-overview.tsx new file mode 100644 index 000000000..80ce6f85c --- /dev/null +++ b/test/error-overview.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import test from 'ava'; +import stripAnsi from 'strip-ansi'; +import {renderToString} from '../src/index.js'; +import ErrorOverview from '../src/components/ErrorOverview.js'; + +const createErrorWithStack = (stack: string) => { + const error = new Error('Oh no'); + error.stack = stack; + + return error; +}; + +test('renders native stack frames as raw lines', t => { + const output = stripAnsi( + renderToString( + , + ), + ); + + t.true(output.includes(' - at native')); + t.false(output.includes('undefined')); +}); + +test('renders named native stack frames as raw lines', t => { + const output = stripAnsi( + renderToString( + , + ), + ); + + t.true(output.includes(' - at foo (native)')); + t.false(output.includes('foo (::)')); + t.false(output.includes('undefined')); +}); + +test('does not emit duplicate key warnings for repeated stack lines', t => { + const consoleErrors: string[] = []; + const originalConsoleError = console.error; + + console.error = (...arguments_: unknown[]) => { + consoleErrors.push(arguments_.join(' ')); + }; + + try { + renderToString( + , + ); + } finally { + console.error = originalConsoleError; + } + + t.false( + consoleErrors.some(error => + error.includes('Encountered two children with the same key'), + ), + ); +});