/
githubmr
/
facebook-react
Обзор
Документация
Войти
/
githubmr
/
facebook-react
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
packages/react/src/__tests__/ReactES6Class-test.js
642 строки
19 KB
Ricky
[flags] remove enableOwnerStacks (#32426)
04 мар 2025, 20:34
Не верифицирован
04 мар 2025, 20:34
e0fe347
Код
Авторство
О чём код?
/** * Copyright (c) Meta Platforms, Inc. and 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'; let PropTypes; let React; let ReactDOM; let ReactDOMClient; let assertConsoleErrorDev; let assertConsoleWarnDev; describe('ReactES6Class', () => { let container; let root; const freeze = function (expectation) { Object.freeze(expectation); return expectation; }; let Inner; let attachedListener = null; let renderedName = null; beforeEach(() => { PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); ({ assertConsoleErrorDev, assertConsoleWarnDev, } = require('internal-test-utils')); container = document.createElement('div'); root = ReactDOMClient.createRoot(container); attachedListener = null; renderedName = null; Inner = class extends React.Component { getName() { return this.props.name; } render() { attachedListener = this.props.onClick; renderedName = this.props.name; return <div className={this.props.name} />; } }; }); function runTest(element, expectedTag, expectedClassName) { ReactDOM.flushSync(() => root.render(element)); expect(container.firstChild).not.toBeNull(); expect(container.firstChild.tagName).toBe(expectedTag); expect(container.firstChild.className).toBe(expectedClassName); } it('preserves the name of the class for use in error messages', () => { class Foo extends React.Component {} expect(Foo.name).toBe('Foo'); }); it('throws if no render function is defined', () => { class Foo extends React.Component {} const caughtErrors = []; function errorHandler(event) { event.preventDefault(); caughtErrors.push(event.error); } window.addEventListener('error', errorHandler); try { ReactDOM.flushSync(() => root.render(<Foo />)); assertConsoleErrorDev([ // A failed component renders twice in DEV in concurrent mode 'No `render` method found on the Foo instance: ' + 'you may have forgotten to define `render`.\n' + ' in Foo (at **)', 'No `render` method found on the Foo instance: ' + 'you may have forgotten to define `render`.\n' + ' in Foo (at **)', ]); } finally { window.removeEventListener('error', errorHandler); } expect(caughtErrors).toEqual([ expect.objectContaining({ message: expect.stringContaining('is not a function'), }), ]); }); it('renders a simple stateless component with prop', () => { class Foo extends React.Component { render() { return <Inner name={this.props.bar} />; } } runTest(<Foo bar="foo" />, 'DIV', 'foo'); runTest(<Foo bar="bar" />, 'DIV', 'bar'); }); it('renders based on state using initial values in this.props', () => { class Foo extends React.Component { constructor(props) { super(props); this.state = {bar: this.props.initialValue}; } render() { return <span className={this.state.bar} />; } } runTest(<Foo initialValue="foo" />, 'SPAN', 'foo'); }); it('renders based on state using props in the constructor', () => { class Foo extends React.Component { constructor(props) { super(props); this.state = {bar: props.initialValue}; } changeState() { this.setState({bar: 'bar'}); } render() { if (this.state.bar === 'foo') { return <div className="foo" />; } return <span className={this.state.bar} />; } } const ref = React.createRef(); runTest(<Foo initialValue="foo" ref={ref} />, 'DIV', 'foo'); ReactDOM.flushSync(() => ref.current.changeState()); runTest(<Foo />, 'SPAN', 'bar'); }); it('sets initial state with value returned by static getDerivedStateFromProps', () => { class Foo extends React.Component { state = {}; static getDerivedStateFromProps(nextProps, prevState) { return { foo: nextProps.foo, bar: 'bar', }; } render() { return <div className={`${this.state.foo} ${this.state.bar}`} />; } } runTest(<Foo foo="foo" />, 'DIV', 'foo bar'); }); it('warns if getDerivedStateFromProps is not static', () => { class Foo extends React.Component { getDerivedStateFromProps() { return {}; } render() { return <div />; } } ReactDOM.flushSync(() => root.render(<Foo foo="foo" />)); assertConsoleErrorDev([ 'Foo: getDerivedStateFromProps() is defined as an instance method ' + 'and will be ignored. Instead, declare it as a static method.\n' + ' in Foo (at **)', ]); }); it('warns if getDerivedStateFromError is not static', () => { class Foo extends React.Component { getDerivedStateFromError() { return {}; } render() { return <div />; } } ReactDOM.flushSync(() => root.render(<Foo foo="foo" />)); assertConsoleErrorDev([ 'Foo: getDerivedStateFromError() is defined as an instance method ' + 'and will be ignored. Instead, declare it as a static method.\n' + ' in Foo (at **)', ]); }); it('warns if getSnapshotBeforeUpdate is static', () => { class Foo extends React.Component { static getSnapshotBeforeUpdate() {} render() { return <div />; } } ReactDOM.flushSync(() => root.render(<Foo foo="foo" />)); assertConsoleErrorDev([ 'Foo: getSnapshotBeforeUpdate() is defined as a static method ' + 'and will be ignored. Instead, declare it as an instance method.\n' + ' in Foo (at **)', ]); }); it('warns if state not initialized before static getDerivedStateFromProps', () => { class Foo extends React.Component { static getDerivedStateFromProps(nextProps, prevState) { return { foo: nextProps.foo, bar: 'bar', }; } render() { return <div className={`${this.state.foo} ${this.state.bar}`} />; } } ReactDOM.flushSync(() => root.render(<Foo foo="foo" />)); assertConsoleErrorDev([ '`Foo` uses `getDerivedStateFromProps` but its initial state is ' + 'undefined. This is not recommended. Instead, define the initial state by ' + 'assigning an object to `this.state` in the constructor of `Foo`. ' + 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.\n' + ' in Foo (at **)', ]); }); it('updates initial state with values returned by static getDerivedStateFromProps', () => { class Foo extends React.Component { state = { foo: 'foo', bar: 'bar', }; static getDerivedStateFromProps(nextProps, prevState) { return { foo: `not-${prevState.foo}`, }; } render() { return <div className={`${this.state.foo} ${this.state.bar}`} />; } } runTest(<Foo />, 'DIV', 'not-foo bar'); }); it('renders updated state with values returned by static getDerivedStateFromProps', () => { class Foo extends React.Component { state = { value: 'initial', }; static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.update) { return { value: 'updated', }; } return null; } render() { return <div className={this.state.value} />; } } runTest(<Foo update={false} />, 'DIV', 'initial'); runTest(<Foo update={true} />, 'DIV', 'updated'); }); if (!require('shared/ReactFeatureFlags').disableLegacyContext) { it('renders based on context in the constructor', () => { class Foo extends React.Component { constructor(props, context) { super(props, context); this.state = {tag: context.tag, className: this.context.className}; } render() { const Tag = this.state.tag; return <Tag className={this.state.className} />; } } Foo.contextTypes = { tag: PropTypes.string, className: PropTypes.string, }; class Outer extends React.Component { getChildContext() { return {tag: 'span', className: 'foo'}; } render() { return <Foo />; } } Outer.childContextTypes = { tag: PropTypes.string, className: PropTypes.string, }; runTest(<Outer />, 'SPAN', 'foo'); assertConsoleErrorDev([ 'Outer uses the legacy childContextTypes API which will soon be removed. ' + 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' + ' in Outer (at **)', 'Foo uses the legacy contextTypes API which will soon be removed. ' + 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' + ' in Outer (at **)', ]); }); } it('renders only once when setting state in componentWillMount', () => { let renderCount = 0; class Foo extends React.Component { constructor(props) { super(props); this.state = {bar: props.initialValue}; } UNSAFE_componentWillMount() { this.setState({bar: 'bar'}); } render() { renderCount++; return <span className={this.state.bar} />; } } runTest(<Foo initialValue="foo" />, 'SPAN', 'bar'); expect(renderCount).toBe(1); }); it('should warn with non-object in the initial state property', () => { [['an array'], 'a string', 1234].forEach(function (state) { class Foo extends React.Component { constructor() { super(); this.state = state; } render() { return <span />; } } runTest(<Foo />, 'SPAN', ''); assertConsoleErrorDev([ 'Foo.state: must be set to an object or null\n in Foo (at **)', ]); }); }); it('should render with null in the initial state property', () => { class Foo extends React.Component { constructor() { super(); this.state = null; } render() { return <span />; } } runTest(<Foo />, 'SPAN', ''); }); it('setState through an event handler', () => { class Foo extends React.Component { constructor(props) { super(props); this.state = {bar: props.initialValue}; } handleClick() { this.setState({bar: 'bar'}); } render() { return ( <Inner name={this.state.bar} onClick={this.handleClick.bind(this)} /> ); } } runTest(<Foo initialValue="foo" />, 'DIV', 'foo'); ReactDOM.flushSync(() => attachedListener()); expect(renderedName).toBe('bar'); }); it('should not implicitly bind event handlers', () => { class Foo extends React.Component { constructor(props) { super(props); this.state = {bar: props.initialValue}; } handleClick() { this.setState({bar: 'bar'}); } render() { return <Inner name={this.state.bar} onClick={this.handleClick} />; } } runTest(<Foo initialValue="foo" />, 'DIV', 'foo'); expect(attachedListener).toThrow(); }); it('renders using forceUpdate even when there is no state', () => { class Foo extends React.Component { constructor(props) { super(props); this.mutativeValue = props.initialValue; } handleClick() { this.mutativeValue = 'bar'; this.forceUpdate(); } render() { return ( <Inner name={this.mutativeValue} onClick={this.handleClick.bind(this)} /> ); } } runTest(<Foo initialValue="foo" />, 'DIV', 'foo'); ReactDOM.flushSync(() => attachedListener()); expect(renderedName).toBe('bar'); }); it('will call all the normal life cycle methods', () => { let lifeCycles = []; class Foo extends React.Component { constructor() { super(); this.state = {}; } UNSAFE_componentWillMount() { lifeCycles.push('will-mount'); } componentDidMount() { lifeCycles.push('did-mount'); } UNSAFE_componentWillReceiveProps(nextProps) { lifeCycles.push('receive-props', nextProps); } shouldComponentUpdate(nextProps, nextState) { lifeCycles.push('should-update', nextProps, nextState); return true; } UNSAFE_componentWillUpdate(nextProps, nextState) { lifeCycles.push('will-update', nextProps, nextState); } componentDidUpdate(prevProps, prevState) { lifeCycles.push('did-update', prevProps, prevState); } componentWillUnmount() { lifeCycles.push('will-unmount'); } render() { return <span className={this.props.value} />; } } runTest(<Foo value="foo" />, 'SPAN', 'foo'); expect(lifeCycles).toEqual(['will-mount', 'did-mount']); lifeCycles = []; // reset runTest(<Foo value="bar" />, 'SPAN', 'bar'); // prettier-ignore expect(lifeCycles).toEqual([ 'receive-props', freeze({value: 'bar'}), 'should-update', freeze({value: 'bar'}), {}, 'will-update', freeze({value: 'bar'}), {}, 'did-update', freeze({value: 'foo'}), {}, ]); lifeCycles = []; // reset ReactDOM.flushSync(() => root.unmount()); expect(lifeCycles).toEqual(['will-unmount']); }); if (!require('shared/ReactFeatureFlags').disableLegacyContext) { it('warns when classic properties are defined on the instance, but does not invoke them.', () => { let getDefaultPropsWasCalled = false; let getInitialStateWasCalled = false; class Foo extends React.Component { constructor() { super(); this.contextTypes = {}; this.contextType = {}; } getInitialState() { getInitialStateWasCalled = true; return {}; } getDefaultProps() { getDefaultPropsWasCalled = true; return {}; } render() { return <span className="foo" />; } } runTest(<Foo />, 'SPAN', 'foo'); assertConsoleErrorDev([ 'getInitialState was defined on Foo, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Did you mean to define a state property instead?\n' + ' in Foo (at **)', 'getDefaultProps was defined on Foo, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Use a static property to define defaultProps instead.\n' + ' in Foo (at **)', 'contextType was defined as an instance property on Foo. ' + 'Use a static property to define contextType instead.\n' + ' in Foo (at **)', 'contextTypes was defined as an instance property on Foo. ' + 'Use a static property to define contextTypes instead.\n' + ' in Foo (at **)', ]); expect(getInitialStateWasCalled).toBe(false); expect(getDefaultPropsWasCalled).toBe(false); }); } it('does not warn about getInitialState() on class components if state is also defined.', () => { class Foo extends React.Component { state = this.getInitialState(); getInitialState() { return {}; } render() { return <span className="foo" />; } } runTest(<Foo />, 'SPAN', 'foo'); }); it('should warn when misspelling shouldComponentUpdate', () => { class NamedComponent extends React.Component { componentShouldUpdate() { return false; } render() { return <span className="foo" />; } } runTest(<NamedComponent />, 'SPAN', 'foo'); assertConsoleErrorDev([ 'NamedComponent has a method called componentShouldUpdate(). Did you ' + 'mean shouldComponentUpdate()? The name is phrased as a question ' + 'because the function is expected to return a value.\n' + ' in NamedComponent (at **)', ]); }); it('should warn when misspelling componentWillReceiveProps', () => { class NamedComponent extends React.Component { componentWillRecieveProps() { return false; } render() { return <span className="foo" />; } } runTest(<NamedComponent />, 'SPAN', 'foo'); assertConsoleErrorDev([ 'NamedComponent has a method called componentWillRecieveProps(). Did ' + 'you mean componentWillReceiveProps()?\n' + ' in NamedComponent (at **)', ]); }); it('should warn when misspelling UNSAFE_componentWillReceiveProps', () => { class NamedComponent extends React.Component { UNSAFE_componentWillRecieveProps() { return false; } render() { return <span className="foo" />; } } runTest(<NamedComponent />, 'SPAN', 'foo'); assertConsoleErrorDev([ 'NamedComponent has a method called UNSAFE_componentWillRecieveProps(). ' + 'Did you mean UNSAFE_componentWillReceiveProps()?\n' + ' in NamedComponent (at **)', ]); }); it('should throw AND warn when trying to access classic APIs', () => { const ref = React.createRef(); runTest(<Inner name="foo" ref={ref} />, 'DIV', 'foo'); expect(() => ref.current.replaceState({})).toThrow(); assertConsoleWarnDev( [ 'replaceState(...) is deprecated in plain JavaScript React classes. ' + 'Refactor your code to use setState instead (see https://github.com/facebook/react/issues/3236).', ], {withoutStack: true}, ); expect(() => ref.current.isMounted()).toThrow(); assertConsoleWarnDev( [ 'isMounted(...) is deprecated in plain JavaScript React classes. ' + 'Instead, make sure to clean up subscriptions and pending requests in ' + 'componentWillUnmount to prevent memory leaks.', ], {withoutStack: true}, ); }); if (!require('shared/ReactFeatureFlags').disableLegacyContext) { it('supports this.context passed via getChildContext', () => { class Bar extends React.Component { render() { return <div className={this.context.bar} />; } } Bar.contextTypes = {bar: PropTypes.string}; class Foo extends React.Component { getChildContext() { return {bar: 'bar-through-context'}; } render() { return <Bar />; } } Foo.childContextTypes = {bar: PropTypes.string}; runTest(<Foo />, 'DIV', 'bar-through-context'); assertConsoleErrorDev([ 'Foo uses the legacy childContextTypes API which will soon be removed. ' + 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' + ' in Foo (at **)', 'Bar uses the legacy contextTypes API which will soon be removed. ' + 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' + ' in Foo (at **)', ]); }); } });