Skip to content

Commit 257541f

Browse files
author
Brian Vaughn
committed
First pass at warnWithComponentStack() API proposal
1 parent c05b4b8 commit 257541f

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

packages/react/src/React.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
cloneElementWithValidation,
4646
} from './ReactElementValidator';
4747
import ReactSharedInternals from './ReactSharedInternals';
48+
import warnWithComponentStack from './warnWithComponentStack';
4849
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
4950

5051
const React = {
@@ -90,6 +91,8 @@ const React = {
9091
unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
9192
unstable_Profiler: REACT_PROFILER_TYPE,
9293

94+
unstable_warnWithComponentStack: warnWithComponentStack,
95+
9396
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
9497
};
9598

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails react-core
8+
*/
9+
10+
'use strict';
11+
12+
function normalizeCodeLocInfo(str) {
13+
return str && str.replace(/at .+?:\d+/g, 'at **');
14+
}
15+
16+
function expectWarningToMatch(expectedMessage, expectedStack) {
17+
expect(console.error).toHaveBeenCalledTimes(1);
18+
19+
const [actualMessage, actualStack] = console.error.calls.mostRecent().args;
20+
21+
expect(actualMessage).toBe(expectedMessage);
22+
expect(normalizeCodeLocInfo(actualStack)).toBe(expectedStack);
23+
}
24+
25+
describe('warnWithComponentStack', () => {
26+
let React = null;
27+
let ReactTestRenderer = null;
28+
let scheduler = null;
29+
let warnWithComponentStack = null;
30+
31+
beforeEach(() => {
32+
jest.resetModules();
33+
jest.mock('scheduler', () => require('scheduler/unstable_mock'));
34+
35+
React = require('react');
36+
ReactTestRenderer = require('react-test-renderer');
37+
scheduler = require('scheduler');
38+
39+
warnWithComponentStack = React.unstable_warnWithComponentStack;
40+
41+
spyOnDevAndProd(console, 'error');
42+
});
43+
44+
if (!__DEV__) {
45+
it('passes warnings through to console.error in production mode', () => {
46+
warnWithComponentStack('Warning logged in production mode');
47+
expectWarningToMatch('Warning logged in production mode', undefined);
48+
});
49+
}
50+
51+
if (__DEV__) {
52+
it('does not include component stack when called outside of render', () => {
53+
warnWithComponentStack('Warning logged outside of render');
54+
expectWarningToMatch('Warning logged outside of render', undefined);
55+
});
56+
57+
it('includes component stack when called from a render method', () => {
58+
class Parent extends React.Component {
59+
render() {
60+
return <Child />;
61+
}
62+
}
63+
64+
function Child() {
65+
warnWithComponentStack('Warning logged in child render method');
66+
return null;
67+
}
68+
69+
ReactTestRenderer.create(<Parent />);
70+
71+
expectWarningToMatch(
72+
'Warning logged in child render method',
73+
'\n in Child (at **)' + '\n in Parent (at **)',
74+
);
75+
});
76+
77+
it('includes component stack when called from a render phase lifecycle method', () => {
78+
function Parent() {
79+
return <Child />;
80+
}
81+
82+
class Child extends React.Component {
83+
UNSAFE_componentWillMount() {
84+
warnWithComponentStack('Warning logged in child cWM lifecycle');
85+
}
86+
render() {
87+
return null;
88+
}
89+
}
90+
91+
ReactTestRenderer.create(<Parent />);
92+
93+
expectWarningToMatch(
94+
'Warning logged in child cWM lifecycle',
95+
'\n in Child (at **)' + '\n in Parent (at **)',
96+
);
97+
});
98+
99+
it('includes component stack when called from a commit phase lifecycle method', () => {
100+
function Parent() {
101+
return <Child />;
102+
}
103+
104+
class Child extends React.Component {
105+
componentDidMount() {
106+
warnWithComponentStack('Warning logged in child cDM lifecycle');
107+
}
108+
render() {
109+
return null;
110+
}
111+
}
112+
113+
ReactTestRenderer.create(<Parent />);
114+
115+
expectWarningToMatch(
116+
'Warning logged in child cDM lifecycle',
117+
'\n in Child (at **)' + '\n in Parent (at **)',
118+
);
119+
});
120+
121+
it('includes component stack when called from a passive effect handler', () => {
122+
class Parent extends React.Component {
123+
render() {
124+
return <Child />;
125+
}
126+
}
127+
128+
function Child() {
129+
React.useEffect(() => {
130+
warnWithComponentStack('Warning logged in child render method');
131+
});
132+
return null;
133+
}
134+
135+
ReactTestRenderer.create(<Parent />);
136+
137+
scheduler.flushAll(); // Flush passive effects
138+
139+
expectWarningToMatch(
140+
'Warning logged in child render method',
141+
'\n in Child (at **)' + '\n in Parent (at **)',
142+
);
143+
});
144+
}
145+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import ReactSharedInternals from 'shared/ReactSharedInternals';
9+
10+
let warnWithComponentStack = (...args) => console.error(...args);
11+
if (__DEV__) {
12+
warnWithComponentStack = (...args) => {
13+
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
14+
const stack = ReactDebugCurrentFrame.getStackAddendum();
15+
if (stack !== '') {
16+
console.error(...args, stack);
17+
} else {
18+
console.error(...args);
19+
}
20+
};
21+
}
22+
23+
export default warnWithComponentStack;

0 commit comments

Comments
 (0)