@samitouri / QOS-React / commits / f161ee2eb7

React.warn() and React.error() (#15170)

Brian Vaughn committed Mar 21, 2019 at 14:44 UTC f161ee2eb7e78d6cb3d3878fe1812ac1057fedc6
3 files changed +244
packages/react/src/React.js
+4
@@ -45,6 +45,7 @@ import {
45 cloneElementWithValidation,
46 } from './ReactElementValidator';
47 import ReactSharedInternals from './ReactSharedInternals';
48 +import {error, warn} from './withComponentStack';
49 import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
50
51 const React = {
@@ -65,6 +66,9 @@ const React = {
66 lazy,
67 memo,
68
69 + error,
70 + warn,
71 +
72 useCallback,
73 useContext,
74 useEffect,
packages/react/src/__tests__/withComponentStack-test.js new
+192
@@ -0,0 +1,192 @@
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 expectHelper(spy, prefix, ...expectedArgs) {
17 + const expectedStack = expectedArgs.pop();
18 +
19 + expect(spy).toHaveBeenCalledTimes(1);
20 +
21 + const actualArgs = spy.calls.mostRecent().args;
22 +
23 + let actualStack = undefined;
24 + if (expectedStack !== undefined) {
25 + actualStack = actualArgs.pop();
26 + expect(normalizeCodeLocInfo(actualStack)).toBe(expectedStack);
27 + }
28 +
29 + expect(actualArgs).toHaveLength(expectedArgs.length);
30 + actualArgs.forEach((actualArg, index) => {
31 + const expectedArg = expectedArgs[index];
32 + expect(actualArg).toBe(
33 + index === 0 ? `${prefix}: ${expectedArg}` : expectedArg,
34 + );
35 + });
36 +}
37 +
38 +function expectMessageAndStack(...expectedArgs) {
39 + expectHelper(console.error, 'error', ...expectedArgs);
40 + expectHelper(console.warn, 'warn', ...expectedArgs);
41 +}
42 +
43 +describe('withComponentStack', () => {
44 + let React = null;
45 + let ReactTestRenderer = null;
46 + let error = null;
47 + let scheduler = null;
48 + let warn = null;
49 +
50 + beforeEach(() => {
51 + jest.resetModules();
52 + jest.mock('scheduler', () => require('scheduler/unstable_mock'));
53 +
54 + React = require('react');
55 + ReactTestRenderer = require('react-test-renderer');
56 + scheduler = require('scheduler');
57 +
58 + error = React.error;
59 + warn = React.warn;
60 +
61 + spyOnDevAndProd(console, 'error');
62 + spyOnDevAndProd(console, 'warn');
63 + });
64 +
65 + if (!__DEV__) {
66 + it('does nothing in production mode', () => {
67 + error('error');
68 + warn('warning');
69 +
70 + expect(console.error).toHaveBeenCalledTimes(0);
71 + expect(console.warn).toHaveBeenCalledTimes(0);
72 + });
73 + }
74 +
75 + if (__DEV__) {
76 + it('does not include component stack when called outside of render', () => {
77 + error('error: logged outside of render');
78 + warn('warn: logged outside of render');
79 + expectMessageAndStack('logged outside of render', undefined);
80 + });
81 +
82 + it('should support multiple args', () => {
83 + function Component() {
84 + error('error: number:', 123, 'boolean:', true);
85 + warn('warn: number:', 123, 'boolean:', true);
86 + return null;
87 + }
88 +
89 + ReactTestRenderer.create(<Component />);
90 +
91 + expectMessageAndStack(
92 + 'number:',
93 + 123,
94 + 'boolean:',
95 + true,
96 + '\n in Component (at **)',
97 + );
98 + });
99 +
100 + it('includes component stack when called from a render method', () => {
101 + class Parent extends React.Component {
102 + render() {
103 + return <Child />;
104 + }
105 + }
106 +
107 + function Child() {
108 + error('error: logged in child render method');
109 + warn('warn: logged in child render method');
110 + return null;
111 + }
112 +
113 + ReactTestRenderer.create(<Parent />);
114 +
115 + expectMessageAndStack(
116 + 'logged in child render method',
117 + '\n in Child (at **)' + '\n in Parent (at **)',
118 + );
119 + });
120 +
121 + it('includes component stack when called from a render phase lifecycle method', () => {
122 + function Parent() {
123 + return <Child />;
124 + }
125 +
126 + class Child extends React.Component {
127 + UNSAFE_componentWillMount() {
128 + error('error: logged in child cWM lifecycle');
129 + warn('warn: logged in child cWM lifecycle');
130 + }
131 + render() {
132 + return null;
133 + }
134 + }
135 +
136 + ReactTestRenderer.create(<Parent />);
137 +
138 + expectMessageAndStack(
139 + 'logged in child cWM lifecycle',
140 + '\n in Child (at **)' + '\n in Parent (at **)',
141 + );
142 + });
143 +
144 + it('includes component stack when called from a commit phase lifecycle method', () => {
145 + function Parent() {
146 + return <Child />;
147 + }
148 +
149 + class Child extends React.Component {
150 + componentDidMount() {
151 + error('error: logged in child cDM lifecycle');
152 + warn('warn: logged in child cDM lifecycle');
153 + }
154 + render() {
155 + return null;
156 + }
157 + }
158 +
159 + ReactTestRenderer.create(<Parent />);
160 +
161 + expectMessageAndStack(
162 + 'logged in child cDM lifecycle',
163 + '\n in Child (at **)' + '\n in Parent (at **)',
164 + );
165 + });
166 +
167 + it('includes component stack when called from a passive effect handler', () => {
168 + class Parent extends React.Component {
169 + render() {
170 + return <Child />;
171 + }
172 + }
173 +
174 + function Child() {
175 + React.useEffect(() => {
176 + error('error: logged in child render method');
177 + warn('warn: logged in child render method');
178 + });
179 + return null;
180 + }
181 +
182 + ReactTestRenderer.create(<Parent />);
183 +
184 + scheduler.flushAll(); // Flush passive effects
185 +
186 + expectMessageAndStack(
187 + 'logged in child render method',
188 + '\n in Child (at **)' + '\n in Parent (at **)',
189 + );
190 + });
191 + }
192 +});
packages/react/src/withComponentStack.js new
+48
@@ -0,0 +1,48 @@
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 +function noop() {}
11 +
12 +let error = noop;
13 +let warn = noop;
14 +if (__DEV__) {
15 + const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
16 +
17 + error = function() {
18 + const stack = ReactDebugCurrentFrame.getStackAddendum();
19 + if (stack !== '') {
20 + const length = arguments.length;
21 + const args = new Array(length + 1);
22 + for (let i = 0; i < length; i++) {
23 + args[i] = arguments[i];
24 + }
25 + args[length] = stack;
26 + console.error.apply(console, args);
27 + } else {
28 + console.error.apply(console, arguments);
29 + }
30 + };
31 +
32 + warn = function() {
33 + const stack = ReactDebugCurrentFrame.getStackAddendum();
34 + if (stack !== '') {
35 + const length = arguments.length;
36 + const args = new Array(length + 1);
37 + for (let i = 0; i < length; i++) {
38 + args[i] = arguments[i];
39 + }
40 + args[length] = stack;
41 + console.warn.apply(console, args);
42 + } else {
43 + console.warn.apply(console, arguments);
44 + }
45 + };
46 +}
47 +
48 +export {error, warn};