RubyLouvre/anu

View on GitHub
packages/render/dom/__tests__/ReactCompositeComponent-test.js

Summary

Maintainability
F
1 wk
Test Coverage
"use strict";

let ChildUpdates;
let MorphingComponent;
let React;
let ReactDOM;
let ReactDOMServer;
let ReactCurrentOwner;
let ReactTestUtils;
let PropTypes;
let shallowEqual;
let shallowCompare;

describe("ReactCompositeComponent", () => {
    beforeEach(() => {
        jest.resetModules();
        React = require("react");
        ReactDOM = require("react-dom");
        ReactDOMServer = require("react-server-renderer");
        // ReactCurrentOwner = require('react')
        //     .__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner;
        ReactTestUtils = require("test-utils");
        PropTypes = require("prop-types");
        shallowCompare = require("lib/shallowCompare");

        MorphingComponent = class extends React.Component {
            state = { activated: false };

            _toggleActivatedState = () => {
                this.setState({ activated: !this.state.activated });
            };

            render() {
                const toggleActivatedState = this._toggleActivatedState;
                return !this.state.activated ? (
                    <a ref="x" onClick={toggleActivatedState} />
                ) : (
                    <b ref="x" onClick={toggleActivatedState} />
                );
            }
        };

        /**
         * We'll use this to ensure that an old version is not cached when it is
         * reallocated again.
         */
        ChildUpdates = class extends React.Component {
            getAnchor = () => {
                return this.refs.anch;
            };

            render() {
                const className = this.props.anchorClassOn ? "anchorClass" : "";
                return this.props.renderAnchor ? (
                    <a ref="anch" className={className} />
                ) : (
                    <b />
                );
            }
        };
    });

    it("should support module pattern components", () => {
        function Child({ test }) {
         
             return <div>{test}</div>;

        }

        const el = document.createElement("div");
        ReactDOM.render(<Child test="test" />, el);

        expect(el.textContent).toBe("test");
    });

    it("should support rendering to different child types over time", () => {
        const instance = ReactTestUtils.renderIntoDocument(
            <MorphingComponent />
        );
        let el = ReactDOM.findDOMNode(instance);
        expect(el.tagName).toBe("A");

        instance._toggleActivatedState();
        el = ReactDOM.findDOMNode(instance);
        expect(el.tagName).toBe("B");

        instance._toggleActivatedState();
        el = ReactDOM.findDOMNode(instance);
        expect(el.tagName).toBe("A");
    });

    it("should not thrash a server rendered layout with client side one", () => {
        class Child extends React.Component {
            render() {
                return null;
            }
        }

        class Parent extends React.Component {
            render() {
                return (
                    <div>
                        <Child />
                    </div>
                );
            }
        }

        const markup = ReactDOMServer.renderToString(<Parent />);

        // Old API based on heuristic
        let container = document.createElement("div");
        container.innerHTML = markup;
        expect(() =>
            ReactDOM.render(<Parent />, container)
        ).toLowPriorityWarnDev(
            "render(): Calling ReactDOM.render() to hydrate server-rendered markup " +
                "will stop working in React v17. Replace the ReactDOM.render() call " +
                "with ReactDOM.hydrate() if you want React to attach to the server HTML."
        );

        // New explicit API
        container = document.createElement("div");
        container.innerHTML = markup;
        ReactDOM.hydrate(<Parent />, container);
    });

    it("should react to state changes from callbacks", () => {
        const instance = ReactTestUtils.renderIntoDocument(
            <MorphingComponent />
        );
        let el = ReactDOM.findDOMNode(instance);
        expect(el.tagName).toBe("A");

        ReactTestUtils.Simulate.click(el);
        el = ReactDOM.findDOMNode(instance);
        expect(el.tagName).toBe("B");
    });

    it("should rewire refs when rendering to different child types", () => {
        const instance = ReactTestUtils.renderIntoDocument(
            <MorphingComponent />
        );

        expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe("A");
        instance._toggleActivatedState();
        expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe("B");
        instance._toggleActivatedState();
        expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe("A");
    });

    it("should not cache old DOM nodes when switching constructors", () => {
        const container = document.createElement("div");
        const instance = ReactDOM.render(
            <ChildUpdates renderAnchor={true} anchorClassOn={false} />,
            container
        );
        ReactDOM.render(
            // Warm any cache
            <ChildUpdates renderAnchor={true} anchorClassOn={true} />,
            container
        );
        ReactDOM.render(
            // Clear out the anchor
            <ChildUpdates renderAnchor={false} anchorClassOn={true} />,
            container
        );
        ReactDOM.render(
            // rerender
            <ChildUpdates renderAnchor={true} anchorClassOn={false} />,
            container
        );
        expect(instance.getAnchor().className).toBe("");
    });

    it("should use default values for undefined props", () => {
        class Component extends React.Component {
            static defaultProps = { prop: "testKey" };

            render() {
                return <span />;
            }
        }

        const instance1 = ReactTestUtils.renderIntoDocument(<Component />);
        expect(instance1.props).toEqual({ prop: "testKey" });

        const instance2 = ReactTestUtils.renderIntoDocument(
            <Component prop={undefined} />
        );
        expect(instance2.props).toEqual({ prop: "testKey" });

        const instance3 = ReactTestUtils.renderIntoDocument(
            <Component prop={null} />
        );
        expect(instance3.props).toEqual({ prop: null });
    });

    it("should not mutate passed-in props object", () => {
        class Component extends React.Component {
            static defaultProps = { prop: "testKey" };

            render() {
                return <span />;
            }
        }

        const inputProps = {};
        let instance1 = <Component {...inputProps} />;
        instance1 = ReactTestUtils.renderIntoDocument(instance1);
        expect(instance1.props.prop).toBe("testKey");

        // We don't mutate the input, just in case the caller wants to do something
        // with it after using it to instantiate a component
        expect(inputProps.prop).not.toBeDefined();
    });

    it("should warn about `forceUpdate` on not-yet-mounted components", () => {
        class MyComponent extends React.Component {
            constructor(props) {
                super(props);
                this.forceUpdate();
            }
            render() {
                return <div />;
            }
        }

        const container = document.createElement("div");
        expect(() => ReactDOM.render(<MyComponent />, container)).toWarnDev(
            "Warning: Can't call forceUpdate on a component that is not yet mounted. " +
                "This is a no-op, but it might indicate a bug in your application. " +
                "Instead, assign to `this.state` directly or define a `state = {};` " +
                "class property with the desired state in the MyComponent component."
        );

        // No additional warning should be recorded
        const container2 = document.createElement("div");
        ReactDOM.render(<MyComponent />, container2);
    });

    it("should warn about `setState` on not-yet-mounted components", () => {
        class MyComponent extends React.Component {
            constructor(props) {
                super(props);
                this.setState();
            }
            render() {
                return <div />;
            }
        }

        const container = document.createElement("div");
        expect(() => ReactDOM.render(<MyComponent />, container)).toWarnDev(
            "Warning: Can't call setState on a component that is not yet mounted. " +
                "This is a no-op, but it might indicate a bug in your application. " +
                "Instead, assign to `this.state` directly or define a `state = {};` " +
                "class property with the desired state in the MyComponent component."
        );

        // No additional warning should be recorded
        const container2 = document.createElement("div");
        ReactDOM.render(<MyComponent />, container2);
    });

    it("should warn about `forceUpdate` on unmounted components", () => {
        const container = document.createElement("div");
        document.body.appendChild(container);

        class Component extends React.Component {
            render() {
                return <div />;
            }
        }

        let instance = <Component />;
        expect(instance.forceUpdate).not.toBeDefined();

        instance = ReactDOM.render(instance, container);
        instance.forceUpdate();

        ReactDOM.unmountComponentAtNode(container);

        expect(() => instance.forceUpdate()).toWarnDev(
            "Warning: Can't call setState (or forceUpdate) on an unmounted " +
                "component. This is a no-op, but it indicates a memory leak in your " +
                "application. To fix, cancel all subscriptions and asynchronous " +
                "tasks in the componentWillUnmount method.\n" +
                "    in Component (at **)"
        );

        // No additional warning should be recorded
        instance.forceUpdate();
    });

    it("should warn about `setState` on unmounted components", () => {
        const container = document.createElement("div");
        document.body.appendChild(container);

        let renders = 0;

        class Component extends React.Component {
            state = { value: 0 };

            render() {
                renders++;
                return <div />;
            }
        }

        let instance;
        ReactDOM.render(
            <div>
                <span>
                    <Component ref={c => (instance = c || instance)} />
                </span>
            </div>,
            container
        );

        expect(renders).toBe(1);

        instance.setState({ value: 1 });

        expect(renders).toBe(2);

        ReactDOM.render(<div />, container);

        expect(() => {
            instance.setState({ value: 2 });
        }).toWarnDev(
            "Warning: Can't call setState (or forceUpdate) on an unmounted " +
                "component. This is a no-op, but it indicates a memory leak in your " +
                "application. To fix, cancel all subscriptions and asynchronous " +
                "tasks in the componentWillUnmount method.\n" +
                "    in Component (at **)\n" +
                "    in span"
        );

        expect(renders).toBe(2);
    });

    it("should silently allow `setState`, not call cb on unmounting components", () => {
        let cbCalled = false;
        const container = document.createElement("div");
        document.body.appendChild(container);

        class Component extends React.Component {
            state = { value: 0 };

            componentWillUnmount() {
                expect(() => {
                    this.setState({ value: 2 }, function() {
                        cbCalled = true;
                    });
                }).not.toThrow();
            }

            render() {
                return <div />;
            }
        }

        const instance = ReactDOM.render(<Component />, container);
        instance.setState({ value: 1 });

        ReactDOM.unmountComponentAtNode(container);
        expect(cbCalled).toBe(false);
    });

    it("should warn when rendering a class with a render method that does not extend React.Component", () => {
        const container = document.createElement("div");
        class ClassWithRenderNotExtended {
            render() {
                return <div />;
            }
        }
        //anujs没有这个限制,只要返回JSX就行了

        expect(() => {
            expect(() => {
                ReactDOM.render(<ClassWithRenderNotExtended />, container);
            }).toWarnDev(
                `ClassWithRenderNotExtended doesn't extend React.Component`
            );
        }).toThrow();
    });

    it("should warn about `setState` in render", () => {
        const container = document.createElement("div");

        let renderedState = -1;
        let renderPasses = 0;

        class Component extends React.Component {
            state = { value: 0 };

            render() {
                renderPasses++;
                renderedState = this.state.value;
                if (this.state.value === 0) {
                    this.setState({ value: 1 });
                }
                return <div />;
            }
        }

        let instance;

        expect(() => {
            instance = ReactDOM.render(<Component />, container);
        }).toWarnDev(
            "Cannot update during an existing state transition (such as within " +
                "`render` or another component's constructor). Render methods should " +
                "be a pure function of props and state; constructor side-effects are " +
                "an anti-pattern, but can be moved to `componentWillMount`."
        );

        // The setState call is queued and then executed as a second pass. This
        // behavior is undefined though so we're free to change it to suit the
        // implementation details.
        expect(renderPasses).toBe(2);
        expect(renderedState).toBe(1);
        expect(instance.state.value).toBe(1);

        // Forcing a rerender anywhere will cause the update to happen.
        const instance2 = ReactDOM.render(<Component prop={123} />, container);
        expect(instance).toBe(instance2);
        expect(renderedState).toBe(1);
        expect(instance2.state.value).toBe(1);

        // Test deduplication; (no additional warnings are expected).
        ReactDOM.unmountComponentAtNode(container);
        ReactDOM.render(<Component prop={123} />, container);
    });

    it("should warn about `setState` in getChildContext", () => {
        const container = document.createElement("div");

        let renderPasses = 0;

        class Component extends React.Component {
            state = { value: 0 };

            getChildContext() {
                if (this.state.value === 0) {
                    this.setState({ value: 1 });
                }
            }

            render() {
                renderPasses++;
                return <div />;
            }
        }
        Component.childContextTypes = {};

        let instance;

        expect(() => {
            instance = ReactDOM.render(<Component />, container);
        }).toWarnDev(
            "Warning: setState(...): Cannot call setState() inside getChildContext()"
        );

        expect(renderPasses).toBe(2);
        expect(instance.state.value).toBe(1);

        // Test deduplication; (no additional warnings are expected).
        ReactDOM.unmountComponentAtNode(container);
        ReactDOM.render(<Component />, container);
    });

    it("should cleanup even if render() fatals", () => {
        console.log("不测试ReactCurrentOwner");
        return;
        class BadComponent extends React.Component {
            render() {
                throw new Error();
            }
        }

        let instance = <BadComponent />;

        expect(ReactCurrentOwner.current).toBe(null);

        expect(() => {
            instance = ReactTestUtils.renderIntoDocument(instance);
        }).toThrow();

        expect(ReactCurrentOwner.current).toBe(null);
    });

    it("should call componentWillUnmount before unmounting", () => {
        const container = document.createElement("div");
        let innerUnmounted = false;

        class Component extends React.Component {
            render() {
                return (
                    <div>
                        <Inner />
                        Text
                    </div>
                );
            }
        }

        class Inner extends React.Component {
            componentWillUnmount() {
                innerUnmounted = true;
            }

            render() {
                return <div />;
            }
        }

        ReactDOM.render(<Component />, container);
        ReactDOM.unmountComponentAtNode(container);
        expect(innerUnmounted).toBe(true);
    });

    it("should warn when shouldComponentUpdate() returns undefined", () => {
        class Component extends React.Component {
            state = { bogus: false };

            shouldComponentUpdate() {
                return undefined;
            }

            render() {
                return <div />;
            }
        }

        const instance = ReactTestUtils.renderIntoDocument(<Component />);

        expect(() => instance.setState({ bogus: true })).toWarnDev(
            "Warning: Component.shouldComponentUpdate(): Returned undefined instead of a " +
                "boolean value. Make sure to return true or false."
        );
    });

    it("should warn when componentDidUnmount method is defined", () => {
        class Component extends React.Component {
            componentDidUnmount = () => {};

            render() {
                return <div />;
            }
        }

        expect(() =>
            ReactTestUtils.renderIntoDocument(<Component />)
        ).toWarnDev(
            "Warning: Component has a method called " +
                "componentDidUnmount(). But there is no such lifecycle method. " +
                "Did you mean componentWillUnmount()?"
        );
    });

    it("should warn when componentDidReceiveProps method is defined", () => {
        class Component extends React.Component {
            componentDidReceiveProps = () => {};

            render() {
                return <div />;
            }
        }

        expect(() =>
            ReactTestUtils.renderIntoDocument(<Component />)
        ).toWarnDev(
            "Warning: Component has a method called " +
                "componentDidReceiveProps(). But there is no such lifecycle method. " +
                "If you meant to update the state in response to changing props, " +
                "use componentWillReceiveProps(). If you meant to fetch data or " +
                "run side-effects or mutations after React has updated the UI, use componentDidUpdate()."
        );
    });

    it("should warn when defaultProps was defined as an instance property", () => {
        class Component extends React.Component {
            constructor(props) {
                super(props);
                this.defaultProps = { name: "Abhay" };
            }

            render() {
                return <div />;
            }
        }

        expect(() =>
            ReactTestUtils.renderIntoDocument(<Component />)
        ).toWarnDev(
            "Warning: Setting defaultProps as an instance property on Component is not supported " +
                "and will be ignored. Instead, define defaultProps as a static property on Component."
        );
    });

    it("should pass context to children when not owner", () => {
        class Parent extends React.Component {
            render() {
                return (
                    <Child>
                        <Grandchild />
                    </Child>
                );
            }
        }

        class Child extends React.Component {
            static childContextTypes = {
                foo: PropTypes.string
            };

            getChildContext() {
                return {
                    foo: "bar"
                };
            }

            render() {
                return React.Children.only(this.props.children);
            }
        }

        class Grandchild extends React.Component {
            static contextTypes = {
                foo: PropTypes.string
            };

            render() {
                return <div>{this.context.foo}</div>;
            }
        }

        const component = ReactTestUtils.renderIntoDocument(<Parent />);
        expect(ReactDOM.findDOMNode(component).innerHTML).toBe("bar");
    });

    it("should skip update when rerendering element in container", () => {
        window.xxx = true;
        class Parent extends React.Component {
            render() {
                return <div>{this.props.children}</div>;
            }
        }

        let childRenders = 0;

        class Child extends React.Component {
            render() {
                childRenders++;
                return <div />;
            }
        }

        const container = document.createElement("div");
        const child = <Child />;

        ReactDOM.render(<Parent>{child}</Parent>, container);

        ReactDOM.render(<Parent>{child}</Parent>, container);
        window.xxx = false;
        expect(childRenders).toBe(1);
    });

    it("should pass context when re-rendered for static child", () => {
        let parentInstance = null;
        let childInstance = null;

        class Parent extends React.Component {
            static childContextTypes = {
                foo: PropTypes.string,
                flag: PropTypes.bool
            };

            state = {
                flag: false
            };

            getChildContext() {
                return {
                    foo: "bar",
                    flag: this.state.flag
                };
            }

            render() {
                return React.Children.only(this.props.children);
            }
        }

        class Middle extends React.Component {
            render() {
                return this.props.children;
            }
        }

        class Child extends React.Component {
            static contextTypes = {
                foo: PropTypes.string,
                flag: PropTypes.bool
            };

            render() {
                childInstance = this;
                return <span>Child</span>;
            }
        }

        parentInstance = ReactTestUtils.renderIntoDocument(
            <Parent>
                <Middle>
                    <Child />
                </Middle>
            </Parent>
        );

        expect(parentInstance.state.flag).toBe(false);
        expect(childInstance.context).toEqual({ foo: "bar", flag: false });

        parentInstance.setState({ flag: true });
        expect(parentInstance.state.flag).toBe(true);
        expect(childInstance.context).toEqual({ foo: "bar", flag: true });
    });

    it("should pass context when re-rendered for static child within a composite component", () => {
        class Parent extends React.Component {
            static childContextTypes = {
                flag: PropTypes.bool
            };

            state = {
                flag: true
            };

            getChildContext() {
                return {
                    flag: this.state.flag
                };
            }

            render() {
                return <div>{this.props.children}</div>;
            }
        }

        class Child extends React.Component {
            static contextTypes = {
                flag: PropTypes.bool
            };

            render() {
                return <div />;
            }
        }

        class Wrapper extends React.Component {
            render() {
                return (
                    <Parent ref="parent">
                        <Child ref="child" />
                    </Parent>
                );
            }
        }

        const wrapper = ReactTestUtils.renderIntoDocument(<Wrapper />);

        expect(wrapper.refs.parent.state.flag).toEqual(true);
        expect(wrapper.refs.child.context).toEqual({ flag: true });

        // We update <Parent /> while <Child /> is still a static prop relative to this update
        wrapper.refs.parent.setState({ flag: false });

        expect(wrapper.refs.parent.state.flag).toEqual(false);
        expect(wrapper.refs.child.context).toEqual({ flag: false });
    });

    it("should pass context transitively", () => {
        let childInstance = null;
        let grandchildInstance = null;

        class Parent extends React.Component {
            static childContextTypes = {
                foo: PropTypes.string,
                depth: PropTypes.number
            };

            getChildContext() {
                return {
                    foo: "bar",
                    depth: 0
                };
            }

            render() {
                return <Child />;
            }
        }

        class Child extends React.Component {
            static contextTypes = {
                foo: PropTypes.string,
                depth: PropTypes.number
            };

            static childContextTypes = {
                depth: PropTypes.number
            };

            getChildContext() {
                return {
                    depth: this.context.depth + 1
                };
            }

            render() {
                childInstance = this;
                return <Grandchild />;
            }
        }

        class Grandchild extends React.Component {
            static contextTypes = {
                foo: PropTypes.string,
                depth: PropTypes.number
            };

            render() {
                grandchildInstance = this;
                return <div />;
            }
        }

        ReactTestUtils.renderIntoDocument(<Parent />);
        expect(childInstance.context).toEqual({ foo: "bar", depth: 0 });
        expect(grandchildInstance.context).toEqual({ foo: "bar", depth: 1 });
    });

    it("should pass context when re-rendered", () => {
        // "暂不测试unstable API"

        let parentInstance = null;
        let childInstance = null;

        class Parent extends React.Component {
            static childContextTypes = {
                foo: PropTypes.string,
                depth: PropTypes.number
            };

            state = {
                flag: false
            };

            getChildContext() {
                return {
                    foo: "bar",
                    depth: 0
                };
            }

            render() {
                let output = <Child />;
                if (!this.state.flag) {
                    output = <span>Child</span>;
                }
                return output;
            }
        }

        class Child extends React.Component {
            static contextTypes = {
                foo: PropTypes.string,
                depth: PropTypes.number
            };

            render() {
                childInstance = this;
                return <span>Child</span>;
            }
        }

        parentInstance = ReactTestUtils.renderIntoDocument(<Parent />);
        expect(childInstance).toBeNull();

        expect(parentInstance.state.flag).toBe(false);
        ReactDOM.unstable_batchedUpdates(function() {
            parentInstance.setState({ flag: true });
        });
        expect(parentInstance.state.flag).toBe(true);

        expect(childInstance.context).toEqual({ foo: "bar", depth: 0 });
    });

    it("unmasked context propagates through updates", () => {
        class Leaf extends React.Component {
            static contextTypes = {
                foo: PropTypes.string.isRequired
            };

            UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
                expect("foo" in nextContext).toBe(true);
            }

            shouldComponentUpdate(nextProps, nextState, nextContext) {
                expect("foo" in nextContext).toBe(true);
                return true;
            }

            render() {
                return <span>{this.context.foo}</span>;
            }
        }

        class Intermediary extends React.Component {
            UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
                expect("foo" in nextContext).toBe(false);
            }

            shouldComponentUpdate(nextProps, nextState, nextContext) {
                expect("foo" in nextContext).toBe(false);
                return true;
            }

            render() {
                return <Leaf />;
            }
        }

        class Parent extends React.Component {
            static childContextTypes = {
                foo: PropTypes.string
            };

            getChildContext() {
                return {
                    foo: this.props.cntxt
                };
            }

            render() {
                return <Intermediary />;
            }
        }

        const div = document.createElement("div");
        ReactDOM.render(<Parent cntxt="noise" />, div);
        expect(div.children[0].innerHTML).toBe("noise");
        console.log("不支持修改DOM");
        // div.children[0].innerHTML = 'aliens';
        div.children[0].id = "aliens";
        expect(div.children[0].innerHTML).toBe("noise");
        expect(div.children[0].id).toBe("aliens");
        ReactDOM.render(<Parent cntxt="bar" />, div);
        expect(div.children[0].innerHTML).toBe("bar");
        expect(div.children[0].id).toBe("aliens");
    });

    it("should trigger componentWillReceiveProps for context changes", () => {
        let contextChanges = 0;
        let propChanges = 0;

        class GrandChild extends React.Component {
            static contextTypes = {
                foo: PropTypes.string.isRequired
            };

            UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
                expect("foo" in nextContext).toBe(true);

                if (nextProps !== this.props) {
                    propChanges++;
                }

                if (nextContext !== this.context) {
                    contextChanges++;
                }
            }

            render() {
                return (
                    <span className="grand-child">{this.props.children}</span>
                );
            }
        }

        class ChildWithContext extends React.Component {
            static contextTypes = {
                foo: PropTypes.string.isRequired
            };

            UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
                expect("foo" in nextContext).toBe(true);

                if (nextProps !== this.props) {
                    propChanges++;
                }

                if (nextContext !== this.context) {
                    contextChanges++;
                }
            }

            render() {
                return <div className="child-with">{this.props.children}</div>;
            }
        }

        class ChildWithoutContext extends React.Component {
            UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
                expect("foo" in nextContext).toBe(false);

                if (nextProps !== this.props) {
                    propChanges++;
                }

                if (nextContext !== this.context) {
                    contextChanges++;
                }
            }

            render() {
                return (
                    <div className="child-without">{this.props.children}</div>
                );
            }
        }

        class Parent extends React.Component {
            static childContextTypes = {
                foo: PropTypes.string
            };

            state = {
                foo: "abc"
            };

            getChildContext() {
                return {
                    foo: this.state.foo
                };
            }

            render() {
                return <div className="parent">{this.props.children}</div>;
            }
        }

        const div = document.createElement("div");

        let parentInstance = null;
        ReactDOM.render(
            <Parent ref={inst => (parentInstance = inst)}>
                <ChildWithoutContext>
                    A1
                    <GrandChild>A2</GrandChild>
                </ChildWithoutContext>

                <ChildWithContext>
                    B1
                    <GrandChild>B2</GrandChild>
                </ChildWithContext>
            </Parent>,
            div
        );

        parentInstance.setState({
            foo: "def"
        });

        expect(propChanges).toBe(0);
        expect(contextChanges).toBe(3); // ChildWithContext, GrandChild x 2
    });

    it("should disallow nested render calls", () => {
        class Inner extends React.Component {
            render() {
                return <div />;
            }
        }

        class Outer extends React.Component {
            render() {
                ReactTestUtils.renderIntoDocument(<Inner />);
                return <div />;
            }
        }

        expect(() => ReactTestUtils.renderIntoDocument(<Outer />)).toWarnDev(
            "Render methods should be a pure function of props and state; " +
                "triggering nested component updates from render is not allowed. If " +
                "necessary, trigger nested updates in componentDidUpdate.\n\nCheck the " +
                "render method of Outer."
        );
    });

    it("only renders once if updated in componentWillReceiveProps", () => {
        let renders = 0;

        class Component extends React.Component {
            state = { updated: false };

            UNSAFE_componentWillReceiveProps(props) {
                expect(props.update).toBe(1);
                expect(renders).toBe(1);
                this.setState({ updated: true });
                expect(renders).toBe(1);
            }

            render() {
                renders++;
                return <div />;
            }
        }

        const container = document.createElement("div");
        const instance = ReactDOM.render(<Component update={0} />, container);
        expect(renders).toBe(1);
        expect(instance.state.updated).toBe(false);
        ReactDOM.render(<Component update={1} />, container);
        expect(renders).toBe(2);
        expect(instance.state.updated).toBe(true);
    });

    it("only renders once if updated in componentWillReceiveProps when batching", () => {
        // "暂不测试unstable API"
        return;
        let renders = 0;

        class Component extends React.Component {
            state = { updated: false };

            UNSAFE_componentWillReceiveProps(props) {
                expect(props.update).toBe(1);
                expect(renders).toBe(1);
                this.setState({ updated: true });
                expect(renders).toBe(1);
            }

            render() {
                renders++;
                return <div />;
            }
        }

        const container = document.createElement("div");
        const instance = ReactDOM.render(<Component update={0} />, container);
        expect(renders).toBe(1);
        expect(instance.state.updated).toBe(false);
        ReactDOM.unstable_batchedUpdates(() => {
            ReactDOM.render(<Component update={1} />, container);
        });
        expect(renders).toBe(2);
        expect(instance.state.updated).toBe(true);
    });

    it("should update refs if shouldComponentUpdate gives false", () => {
        class Static extends React.Component {
            shouldComponentUpdate() {
                return false;
            }

            render() {
                return <div>{this.props.children}</div>;
            }
        }

        class Component extends React.Component {
            render() {
                if (this.props.flipped) {
                    return (
                        <div>
                            <Static ref="static0" key="B">
                                B (ignored)
                            </Static>
                            <Static ref="static1" key="A">
                                A (ignored)
                            </Static>
                        </div>
                    );
                } else {
                    return (
                        <div>
                            <Static ref="static0" key="A">
                                A
                            </Static>
                            <Static ref="static1" key="B">
                                B
                            </Static>
                        </div>
                    );
                }
            }
        }

        const container = document.createElement("div");
        const comp = ReactDOM.render(<Component flipped={false} />, container);
        expect(ReactDOM.findDOMNode(comp.refs.static0).textContent).toBe("A");
        expect(ReactDOM.findDOMNode(comp.refs.static1).textContent).toBe("B");

        // When flipping the order, the refs should update even though the actual
        // contents do not
        ReactDOM.render(<Component flipped={true} />, container);
        expect(ReactDOM.findDOMNode(comp.refs.static0).textContent).toBe("B");
        expect(ReactDOM.findDOMNode(comp.refs.static1).textContent).toBe("A");
    });

    it("should allow access to findDOMNode in componentWillUnmount", () => {
        let a = null;
        let b = null;

        class Component extends React.Component {
            componentDidMount() {
                a = ReactDOM.findDOMNode(this);
                expect(a).not.toBe(null);
            }

            componentWillUnmount() {
                b = ReactDOM.findDOMNode(this);
                expect(b).not.toBe(null);
            }

            render() {
                return <div />;
            }
        }

        const container = document.createElement("div");
        expect(a).toBe(container.firstChild);
        ReactDOM.render(<Component />, container);
        ReactDOM.unmountComponentAtNode(container);
        expect(a).toBe(b);
    });

    it("context should be passed down from the parent", () => {
        class Parent extends React.Component {
            static childContextTypes = {
                foo: PropTypes.string
            };

            getChildContext() {
                return {
                    foo: "bar"
                };
            }

            render() {
                return <div>{this.props.children}</div>;
            }
        }

        class Component extends React.Component {
            static contextTypes = {
                foo: PropTypes.string.isRequired
            };

            render() {
                return <div />;
            }
        }

        const div = document.createElement("div");
        ReactDOM.render(
            <Parent>
                <Component />
            </Parent>,
            div
        );
    });

    it("should replace state", () => {
        // 不测试replaceState"
        return;
        class Moo extends React.Component {
            state = { x: 1 };
            render() {
                return <div />;
            }
        }

        const moo = ReactTestUtils.renderIntoDocument(<Moo />);
        // No longer a public API, but we can test that it works internally by
        // reaching into the updater.
        moo.updater.enqueueReplaceState(moo, { y: 2 });
        expect("x" in moo.state).toBe(false);
        expect(moo.state.y).toBe(2);
    });

    it("should support objects with prototypes as state", () => {
        // "暂不测试unstable API"
        return;
        const NotActuallyImmutable = function(str) {
            this.str = str;
        };
        NotActuallyImmutable.prototype.amIImmutable = function() {
            return true;
        };
        class Moo extends React.Component {
            state = new NotActuallyImmutable("first");
            // No longer a public API, but we can test that it works internally by
            // reaching into the updater.
            _replaceState = update =>
                this.updater.enqueueReplaceState(this, update);
            render() {
                return <div />;
            }
        }

        const moo = ReactTestUtils.renderIntoDocument(<Moo />);
        expect(moo.state.str).toBe("first");
        expect(moo.state.amIImmutable()).toBe(true);

        const secondState = new NotActuallyImmutable("second");
        moo._replaceState(secondState);
        expect(moo.state.str).toBe("second");
        expect(moo.state.amIImmutable()).toBe(true);
        expect(moo.state).toBe(secondState);

        moo.setState({ str: "third" });
        expect(moo.state.str).toBe("third");
        // Here we lose the prototype.
        expect(moo.state.amIImmutable).toBe(undefined);

        // When more than one state update is enqueued, we have the same behavior
        const fifthState = new NotActuallyImmutable("fifth");
        ReactDOM.unstable_batchedUpdates(function() {
            moo.setState({ str: "fourth" });
            moo._replaceState(fifthState);
        });
        expect(moo.state).toBe(fifthState);

        // When more than one state update is enqueued, we have the same behavior
        const sixthState = new NotActuallyImmutable("sixth");
        ReactDOM.unstable_batchedUpdates(function() {
            moo._replaceState(sixthState);
            moo.setState({ str: "seventh" });
        });
        expect(moo.state.str).toBe("seventh");
        expect(moo.state.amIImmutable).toBe(undefined);
    });

    it("should not warn about unmounting during unmounting", () => {
        const container = document.createElement("div");
        const layer = document.createElement("div");

        class Component extends React.Component {
            componentDidMount() {
                ReactDOM.render(<div />, layer);
            }

            componentWillUnmount() {
                ReactDOM.unmountComponentAtNode(layer);
            }

            render() {
                return <div />;
            }
        }

        class Outer extends React.Component {
            render() {
                return <div>{this.props.children}</div>;
            }
        }

        ReactDOM.render(
            <Outer>
                <Component />
            </Outer>,
            container
        );
        ReactDOM.render(<Outer />, container);
    });

    it("should warn when mutated props are passed", () => {
        const container = document.createElement("div");

        class Foo extends React.Component {
            constructor(props) {
                const _props = { idx: props.idx + "!" };
                super(_props);
            }

            render() {
                return <span />;
            }
        }

        expect(() => ReactDOM.render(<Foo idx="qwe" />, container)).toWarnDev(
            "Foo(...): When calling super() in `Foo`, make sure to pass " +
                "up the same props that your component's constructor was passed."
        );
    });

    it("should only call componentWillUnmount once", () => {
        let app;
        let count = 0;

        class App extends React.Component {
            render() {
                if (this.props.stage === 1) {
                    return <UnunmountableComponent />;
                } else {
                    return null;
                }
            }
        }

        class UnunmountableComponent extends React.Component {
            componentWillUnmount() {
                app.setState({});
                count++;
                throw Error("always fails");
            }

            render() {
                return <div>Hello {this.props.name}</div>;
            }
        }

        const container = document.createElement("div");

        const setRef = ref => {
            if (ref) {
                app = ref;
            }
        };

        expect(() => {
            ReactDOM.render(<App ref={setRef} stage={1} />, container);
            ReactDOM.render(<App ref={setRef} stage={2} />, container);
        }).toThrow();
        expect(count).toBe(1);
    });

    it("prepares new child before unmounting old", () => {
        const log = [];

        class Spy extends React.Component {
            UNSAFE_componentWillMount() {
                log.push(this.props.name + " componentWillMount");
            }
            render() {
                log.push(this.props.name + " render");
                return <div />;
            }
            componentDidMount() {
                log.push(this.props.name + " componentDidMount");
            }
            componentWillUnmount() {
                log.push(this.props.name + " componentWillUnmount");
            }
        }

        class Wrapper extends React.Component {
            render() {
                return <Spy key={this.props.name} name={this.props.name} />;
            }
        }

        const container = document.createElement("div");
        ReactDOM.render(<Wrapper name="A" />, container);
        ReactDOM.render(<Wrapper name="B" />, container);

        expect(log).toEqual([
            "A componentWillMount",
            "A render",
            "A componentDidMount",

            "B componentWillMount",
            "B render",
            "A componentWillUnmount",
            "B componentDidMount"
        ]);
    });

    it("respects a shallow shouldComponentUpdate implementation", () => {
        let renderCalls = 0;
        class PlasticWrap extends React.Component {
            constructor(props, context) {
                super(props, context);
                this.state = {
                    color: "green"
                };
            }

            render() {
                return <Apple color={this.state.color} ref="apple" />;
            }
        }

        class Apple extends React.Component {
            state = {
                cut: false,
                slices: 1
            };

            shouldComponentUpdate(nextProps, nextState) {
                return shallowCompare(this, nextProps, nextState);
            }

            cut() {
                this.setState({
                    cut: true,
                    slices: 10
                });
            }

            eatSlice() {
                this.setState({
                    slices: this.state.slices - 1
                });
            }

            render() {
                renderCalls++;
                return <div />;
            }
        }

        const container = document.createElement("div");
        const instance = ReactDOM.render(<PlasticWrap />, container);
        expect(renderCalls).toBe(1);

        // Do not re-render based on props
        instance.setState({ color: "green" });
        expect(renderCalls).toBe(1);

        // Re-render based on props
        instance.setState({ color: "red" });
        expect(renderCalls).toBe(2);

        // Re-render base on state
        instance.refs.apple.cut();
        expect(renderCalls).toBe(3);

        // No re-render based on state
        instance.refs.apple.cut();
        expect(renderCalls).toBe(3);

        // Re-render based on state again
        instance.refs.apple.eatSlice();
        expect(renderCalls).toBe(4);
    });

    it("does not do a deep comparison for a shallow shouldComponentUpdate implementation", () => {
        function getInitialState() {
            return {
                foo: [1, 2, 3],
                bar: { a: 4, b: 5, c: 6 }
            };
        }

        let renderCalls = 0;
        const initialSettings = getInitialState();

        class Component extends React.Component {
            state = initialSettings;

            shouldComponentUpdate(nextProps, nextState) {
                return shallowCompare(this, nextProps, nextState);
            }

            render() {
                renderCalls++;
                return <div />;
            }
        }

        const container = document.createElement("div");
        const instance = ReactDOM.render(<Component />, container);
        expect(renderCalls).toBe(1);

        // Do not re-render if state is equal
        const settings = {
            foo: initialSettings.foo,
            bar: initialSettings.bar
        };
        instance.setState(settings);
        expect(renderCalls).toBe(1);

        // Re-render because one field changed
        initialSettings.foo = [1, 2, 3];
        instance.setState(initialSettings);
        expect(renderCalls).toBe(2);

        // Re-render because the object changed
        instance.setState(getInitialState());
        expect(renderCalls).toBe(3);
    });

    it("should call setState callback with no arguments", () => {
        let mockArgs;
        class Component extends React.Component {
            componentDidMount() {
                this.setState({}, (...args) => (mockArgs = args));
            }
            render() {
                return false;
            }
        }

        ReactTestUtils.renderIntoDocument(<Component />);
        expect(mockArgs.length).toEqual(0);
    });

    it("this.state should be updated on setState callback inside componentWillMount", () => {
        const div = document.createElement("div");
        let stateSuccessfullyUpdated = false;

        class Component extends React.Component {
            constructor(props, context) {
                super(props, context);
                this.state = {
                    hasUpdatedState: false
                };
            }

            UNSAFE_componentWillMount() {
                this.setState(
                    { hasUpdatedState: true },
                    () =>
                        (stateSuccessfullyUpdated = this.state.hasUpdatedState)
                );
            }

            render() {
                return <div>{this.props.children}</div>;
            }
        }

        ReactDOM.render(<Component />, div);
        expect(stateSuccessfullyUpdated).toBe(true);
    });

    it("should call the setState callback even if shouldComponentUpdate = false", done => {
        const mockFn = jest.fn().mockReturnValue(false);
        const div = document.createElement("div");

        let instance;

        class Component extends React.Component {
            constructor(props, context) {
                super(props, context);
                this.state = {
                    hasUpdatedState: false
                };
            }

            UNSAFE_componentWillMount() {
                instance = this;
            }

            shouldComponentUpdate() {
                return mockFn();
            }

            render() {
                return <div>{this.state.hasUpdatedState}</div>;
            }
        }

        ReactDOM.render(<Component />, div);

        expect(instance).toBeDefined();
        expect(mockFn).not.toBeCalled();

        instance.setState({ hasUpdatedState: true }, () => {
            expect(mockFn).toBeCalled();
            expect(instance.state.hasUpdatedState).toBe(true);
            done();
        });
    });

    it("should return a meaningful warning when constructor is returned", () => {
        class RenderTextInvalidConstructor extends React.Component {
            constructor(props) {
                super(props);
                return { something: false };
            }

            render() {
                return <div />;
            }
        }

        expect(() => {
            expect(() => {
                ReactTestUtils.renderIntoDocument(
                    <RenderTextInvalidConstructor />
                );
            }).toWarnDev(
                "Warning: RenderTextInvalidConstructor(...): No `render` method found on the returned component instance: " +
                    "did you accidentally return an object from the constructor?"
            );
        }).toThrow();
    });

    it("should return error if render is not defined", () => {
        class RenderTestUndefinedRender extends React.Component {}

        expect(() => {
            expect(() => {
                ReactTestUtils.renderIntoDocument(
                    <RenderTestUndefinedRender />
                );
            }).toWarnDev(
                "Warning: RenderTestUndefinedRender(...): No `render` method found on the returned " +
                    "component instance: you may have forgotten to define `render`."
            );
        }).toThrow();
    });

    it("上面的context没变,componentWillReceiveProps不会触发", () => {
        class ListViewDemo extends React.Component {
            static childContextTypes = {
                testItems: PropTypes.array
            };
            constructor(props) {
                super(props);
                this.state = {
                    testItems: [1, 2, 3]
                };
            }
            getChildContext() {
                return { testItems: this.state.testItems };
            }
            render() {
                return (
                    <div>
                        <Hello ref="hello" />
                    </div>
                );
            }
        }
        var list = [];
        class Hello extends React.Component {
            static contextTypes = {
                testItems: PropTypes.array
            };
            constructor(props) {
                super(props);
                this.state = {
                    hello: "world"
                };
            }
            componentWillReceiveProps(nextProps) {
                list.push("componentWillReceiveProps");
            }
            componentDidUpdate() {
                list.push("componentDidUpdate");
            }
            changeHello() {
                this.setState({
                    hello: "woooo"
                });
            }
            render() {
                return (
                    <div ref="div" onClick={this.changeHello.bind(this)}>
                        {this.state.hello}
                    </div>
                );
            }
        }
        var div = document.createElement("div");
        var instance = ReactDOM.render(<ListViewDemo />, div);
        instance.refs.hello.changeHello();
        expect(list).toEqual(["componentDidUpdate"]);
    });
});