UNPKG

hm-react-cli

Version:

Create a Huimei React project by module

1,629 lines (1,341 loc) 54.7 kB
"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 }, () =>