diff --git a/packages/react-native/Libraries/LogBox/UI/LogBoxInspector.js b/packages/react-native/Libraries/LogBox/UI/LogBoxInspector.js index 3f3ae961968b..f76143a3e076 100644 --- a/packages/react-native/Libraries/LogBox/UI/LogBoxInspector.js +++ b/packages/react-native/Libraries/LogBox/UI/LogBoxInspector.js @@ -8,6 +8,7 @@ * @format */ +import Clipboard from '../../Components/Clipboard/Clipboard'; import Keyboard from '../../Components/Keyboard/Keyboard'; import View from '../../Components/View/View'; import StyleSheet from '../../StyleSheet/StyleSheet'; @@ -59,6 +60,47 @@ export default function LogBoxInspector(props: Props): React.Node { LogBoxData.retrySymbolicateLogNow(log); } + function _handleCopy() { + const headerTitleMap = { + warn: 'Console Warning', + error: 'Console Error', + fatal: 'Uncaught Error', + syntax: 'Syntax Error', + component: 'Render Error', + }; + + const title = + log.type ?? + headerTitleMap[log.isComponentError ? 'component' : log.level]; + + const parts = [title, '', log.message.content]; + + if (log.codeFrame != null) { + const location = log.codeFrame.location; + parts.push( + '', + 'Source:', + location != null + ? `${log.codeFrame.fileName} (${location.row}:${location.column})` + : log.codeFrame.fileName, + ); + } + + const stack = log.getAvailableStack(); + if (stack.length > 0) { + parts.push('', 'Call Stack:'); + for (const frame of stack) { + const methodName = frame.methodName ?? '?'; + const file = frame.file ?? '?'; + const lineNumber = + frame.lineNumber != null ? `:${frame.lineNumber}` : ''; + parts.push(`${methodName} (${file}${lineNumber})`); + } + } + + Clipboard.setString(parts.join('\n')); + } + if (log == null) { return null; } @@ -75,6 +117,7 @@ export default function LogBoxInspector(props: Props): React.Node { diff --git a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorFooter.js b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorFooter.js index 9f599c07e7d8..5d5092944e05 100644 --- a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorFooter.js +++ b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorFooter.js @@ -20,6 +20,7 @@ import * as React from 'react'; type Props = Readonly<{ onDismiss: () => void, onMinimize: () => void, + onCopy: () => void, level?: ?LogLevel, }>; @@ -48,6 +49,11 @@ export default function LogBoxInspectorFooter(props: Props): React.Node { text="Minimize" onPress={props.onMinimize} /> + ); } diff --git a/packages/react-native/Libraries/LogBox/UI/__tests__/LogBoxInspectorFooter-test.js b/packages/react-native/Libraries/LogBox/UI/__tests__/LogBoxInspectorFooter-test.js index 9d60b64ec990..9a0cab402c5e 100644 --- a/packages/react-native/Libraries/LogBox/UI/__tests__/LogBoxInspectorFooter-test.js +++ b/packages/react-native/Libraries/LogBox/UI/__tests__/LogBoxInspectorFooter-test.js @@ -27,6 +27,7 @@ describe('LogBoxInspectorFooter', () => { {}} onDismiss={() => {}} + onCopy={() => {}} level="warn" />, ); @@ -39,6 +40,7 @@ describe('LogBoxInspectorFooter', () => { {}} onDismiss={() => {}} + onCopy={() => {}} level="error" />, ); @@ -51,6 +53,7 @@ describe('LogBoxInspectorFooter', () => { {}} onDismiss={() => {}} + onCopy={() => {}} level="fatal" />, ); @@ -63,6 +66,7 @@ describe('LogBoxInspectorFooter', () => { {}} onDismiss={() => {}} + onCopy={() => {}} level="syntax" />, ); diff --git a/packages/react-native/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspector-test.js.snap b/packages/react-native/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspector-test.js.snap index e1ffa1836f24..2c87e93c11d5 100644 --- a/packages/react-native/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspector-test.js.snap +++ b/packages/react-native/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspector-test.js.snap @@ -49,6 +49,7 @@ exports[`LogBoxContainer should render fatal with selectedIndex 2 1`] = ` /> @@ -106,6 +107,7 @@ exports[`LogBoxContainer should render warning with selectedIndex 0 1`] = ` /> diff --git a/packages/react-native/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspectorFooter-test.js.snap b/packages/react-native/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspectorFooter-test.js.snap index 272c6de898a8..57412c533f20 100644 --- a/packages/react-native/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspectorFooter-test.js.snap +++ b/packages/react-native/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxInspectorFooter-test.js.snap @@ -71,6 +71,11 @@ exports[`LogBoxInspectorFooter should render two buttons for error 1`] = ` onPress={[Function]} text="Minimize" /> + `; @@ -100,6 +105,11 @@ exports[`LogBoxInspectorFooter should render two buttons for fatal 1`] = ` onPress={[Function]} text="Minimize" /> + `; @@ -129,5 +139,10 @@ exports[`LogBoxInspectorFooter should render two buttons for warning 1`] = ` onPress={[Function]} text="Minimize" /> + `;