-
Notifications
You must be signed in to change notification settings - Fork 47.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
React.warn() and React.error() (#15170)
- Loading branch information
Showing
3 changed files
with
244 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
192 changes: 192 additions & 0 deletions
192
packages/react/src/__tests__/withComponentStack-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 **)', | ||
); | ||
}); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}; |