Skip to content

Commit

Permalink
React.warn() and React.error() (#15170)
Browse files Browse the repository at this point in the history
  • Loading branch information
bvaughn authored Mar 21, 2019
1 parent 78968bb commit f161ee2
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/react/src/React.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
cloneElementWithValidation,
} from './ReactElementValidator';
import ReactSharedInternals from './ReactSharedInternals';
import {error, warn} from './withComponentStack';
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';

const React = {
Expand All @@ -65,6 +66,9 @@ const React = {
lazy,
memo,

error,
warn,

useCallback,
useContext,
useEffect,
Expand Down
192 changes: 192 additions & 0 deletions packages/react/src/__tests__/withComponentStack-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/

'use strict';

function normalizeCodeLocInfo(str) {
return str && str.replace(/at .+?:\d+/g, 'at **');
}

function expectHelper(spy, prefix, ...expectedArgs) {
const expectedStack = expectedArgs.pop();

expect(spy).toHaveBeenCalledTimes(1);

const actualArgs = spy.calls.mostRecent().args;

let actualStack = undefined;
if (expectedStack !== undefined) {
actualStack = actualArgs.pop();
expect(normalizeCodeLocInfo(actualStack)).toBe(expectedStack);
}

expect(actualArgs).toHaveLength(expectedArgs.length);
actualArgs.forEach((actualArg, index) => {
const expectedArg = expectedArgs[index];
expect(actualArg).toBe(
index === 0 ? `${prefix}: ${expectedArg}` : expectedArg,
);
});
}

function expectMessageAndStack(...expectedArgs) {
expectHelper(console.error, 'error', ...expectedArgs);
expectHelper(console.warn, 'warn', ...expectedArgs);
}

describe('withComponentStack', () => {
let React = null;
let ReactTestRenderer = null;
let error = null;
let scheduler = null;
let warn = null;

beforeEach(() => {
jest.resetModules();
jest.mock('scheduler', () => require('scheduler/unstable_mock'));

React = require('react');
ReactTestRenderer = require('react-test-renderer');
scheduler = require('scheduler');

error = React.error;
warn = React.warn;

spyOnDevAndProd(console, 'error');
spyOnDevAndProd(console, 'warn');
});

if (!__DEV__) {
it('does nothing in production mode', () => {
error('error');
warn('warning');

expect(console.error).toHaveBeenCalledTimes(0);
expect(console.warn).toHaveBeenCalledTimes(0);
});
}

if (__DEV__) {
it('does not include component stack when called outside of render', () => {
error('error: logged outside of render');
warn('warn: logged outside of render');
expectMessageAndStack('logged outside of render', undefined);
});

it('should support multiple args', () => {
function Component() {
error('error: number:', 123, 'boolean:', true);
warn('warn: number:', 123, 'boolean:', true);
return null;
}

ReactTestRenderer.create(<Component />);

expectMessageAndStack(
'number:',
123,
'boolean:',
true,
'\n in Component (at **)',
);
});

it('includes component stack when called from a render method', () => {
class Parent extends React.Component {
render() {
return <Child />;
}
}

function Child() {
error('error: logged in child render method');
warn('warn: logged in child render method');
return null;
}

ReactTestRenderer.create(<Parent />);

expectMessageAndStack(
'logged in child render method',
'\n in Child (at **)' + '\n in Parent (at **)',
);
});

it('includes component stack when called from a render phase lifecycle method', () => {
function Parent() {
return <Child />;
}

class Child extends React.Component {
UNSAFE_componentWillMount() {
error('error: logged in child cWM lifecycle');
warn('warn: logged in child cWM lifecycle');
}
render() {
return null;
}
}

ReactTestRenderer.create(<Parent />);

expectMessageAndStack(
'logged in child cWM lifecycle',
'\n in Child (at **)' + '\n in Parent (at **)',
);
});

it('includes component stack when called from a commit phase lifecycle method', () => {
function Parent() {
return <Child />;
}

class Child extends React.Component {
componentDidMount() {
error('error: logged in child cDM lifecycle');
warn('warn: logged in child cDM lifecycle');
}
render() {
return null;
}
}

ReactTestRenderer.create(<Parent />);

expectMessageAndStack(
'logged in child cDM lifecycle',
'\n in Child (at **)' + '\n in Parent (at **)',
);
});

it('includes component stack when called from a passive effect handler', () => {
class Parent extends React.Component {
render() {
return <Child />;
}
}

function Child() {
React.useEffect(() => {
error('error: logged in child render method');
warn('warn: logged in child render method');
});
return null;
}

ReactTestRenderer.create(<Parent />);

scheduler.flushAll(); // Flush passive effects

expectMessageAndStack(
'logged in child render method',
'\n in Child (at **)' + '\n in Parent (at **)',
);
});
}
});
48 changes: 48 additions & 0 deletions packages/react/src/withComponentStack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import ReactSharedInternals from 'shared/ReactSharedInternals';

function noop() {}

let error = noop;
let warn = noop;
if (__DEV__) {
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;

error = function() {
const stack = ReactDebugCurrentFrame.getStackAddendum();
if (stack !== '') {
const length = arguments.length;
const args = new Array(length + 1);
for (let i = 0; i < length; i++) {
args[i] = arguments[i];
}
args[length] = stack;
console.error.apply(console, args);
} else {
console.error.apply(console, arguments);
}
};

warn = function() {
const stack = ReactDebugCurrentFrame.getStackAddendum();
if (stack !== '') {
const length = arguments.length;
const args = new Array(length + 1);
for (let i = 0; i < length; i++) {
args[i] = arguments[i];
}
args[length] = stack;
console.warn.apply(console, args);
} else {
console.warn.apply(console, arguments);
}
};
}

export {error, warn};

0 comments on commit f161ee2

Please sign in to comment.