ReactComponentLifeCycle-test.jsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. import PropTypes from '../../lib/ReactPropTypes';
  2. import ReactTestUtils from "../../lib/ReactTestUtils";
  3. import React from '../../src/May';
  4. import { render, unmountComponentAtNode, findDOMNode } from '../../src/may-dom/MayDom'
  5. var ReactDOM = {
  6. render: render,
  7. unmountComponentAtNode: unmountComponentAtNode,
  8. findDOMNode: findDOMNode
  9. }
  10. React.render = render;
  11. // import React from "../../dist/ReactANU";
  12. // var ReactDOM = React;
  13. describe("ReactComponentLifeCycle-test", function() {
  14. // this.timeout(200000);
  15. it('should not reuse an instance when it has been unmounted', () => {
  16. var container = document.createElement('div');
  17. class StatefulComponent extends React.Component {
  18. state = {};
  19. render() {
  20. return <div />;
  21. }
  22. }
  23. var element = <StatefulComponent />;
  24. var firstInstance = ReactDOM.render(element, container);
  25. ReactDOM.unmountComponentAtNode(container);
  26. var secondInstance = ReactDOM.render(element, container);
  27. expect(firstInstance).not.toBe(secondInstance);
  28. });
  29. /**
  30. * If a state update triggers rerendering that in turn fires an onDOMReady,
  31. * that second onDOMReady should not fail.
  32. */
  33. it('it should fire onDOMReady when already in onDOMReady', () => {
  34. var _testJournal = [];
  35. class Child extends React.Component {
  36. componentDidMount() {
  37. _testJournal.push('Child:onDOMReady');
  38. }
  39. render() {
  40. return <div />;
  41. }
  42. }
  43. class SwitcherParent extends React.Component {
  44. constructor(props) {
  45. super(props);
  46. _testJournal.push('SwitcherParent:getInitialState');
  47. this.state = {showHasOnDOMReadyComponent: false};
  48. }
  49. componentDidMount() {
  50. _testJournal.push('SwitcherParent:onDOMReady');
  51. this.switchIt();
  52. }
  53. switchIt = () => {
  54. this.setState({showHasOnDOMReadyComponent: true});
  55. };
  56. render() {
  57. return (
  58. <div>
  59. {this.state.showHasOnDOMReadyComponent ? <Child /> : <div />}
  60. </div>
  61. );
  62. }
  63. }
  64. ReactTestUtils.renderIntoDocument(<SwitcherParent />);
  65. expect(_testJournal).toEqual([
  66. 'SwitcherParent:getInitialState',
  67. 'SwitcherParent:onDOMReady',
  68. 'Child:onDOMReady',
  69. ]);
  70. });
  71. // You could assign state here, but not access members of it, unless you
  72. // had provided a getInitialState method.
  73. it('throws when accessing state in componentWillMount', () => {
  74. class StatefulComponent extends React.Component {
  75. componentWillMount() {
  76. void this.state.yada;
  77. }
  78. render() {
  79. return <div />;
  80. }
  81. }
  82. var instance = <StatefulComponent />;
  83. expect(function() {
  84. instance = ReactTestUtils.renderIntoDocument(instance);
  85. }).toThrow();
  86. });
  87. it('should allow update state inside of componentWillMount', () => {
  88. class StatefulComponent extends React.Component {
  89. componentWillMount() {
  90. this.setState({stateField: 'something'});
  91. }
  92. render() {
  93. return <div />;
  94. }
  95. }
  96. var instance = <StatefulComponent />;
  97. expect(function() {
  98. instance = ReactTestUtils.renderIntoDocument(instance);
  99. }).not.toThrow();
  100. });
  101. it('should not allow update state inside of getInitialState', () => {
  102. class StatefulComponent extends React.Component {
  103. constructor(props, context) {
  104. super(props, context);
  105. this.state = {stateField: 'somethingelse'};
  106. // this.setState({stateField: 'something'});
  107. }
  108. render() {
  109. return <div>{this.state.stateField}</div>;
  110. }
  111. }
  112. var container = document.createElement('div');
  113. ReactDOM.render(<StatefulComponent />, container);
  114. expect(container.textContent).toBe("somethingelse");
  115. });
  116. it('should correctly determine if a component is mounted', () => {
  117. class Component extends React.Component {
  118. componentWillMount() {
  119. expect(this.isMounted()).toBeFalsy();
  120. }
  121. componentDidMount() {
  122. expect(this.isMounted()).toBeTruthy();
  123. }
  124. render() {
  125. expect(this.isMounted()).toBeFalsy();
  126. return <div />;
  127. }
  128. }
  129. var element = <Component />;
  130. var instance = ReactTestUtils.renderIntoDocument(element);
  131. expect(instance.isMounted()).toBeTruthy();
  132. });
  133. it('should correctly determine if a null component is mounted', () => {
  134. class Component extends React.Component {
  135. componentWillMount() {
  136. expect(this.isMounted()).toBeFalsy();
  137. }
  138. componentDidMount() {
  139. expect(this.isMounted()).toBeTruthy();
  140. }
  141. render() {
  142. expect(this.isMounted()).toBeFalsy();
  143. return null;
  144. }
  145. }
  146. var element = <Component />;
  147. var instance = ReactTestUtils.renderIntoDocument(element);
  148. expect(instance.isMounted()).toBeTruthy();
  149. })
  150. it('isMounted should return false when unmounted', () => {
  151. class Component extends React.Component {
  152. render() {
  153. return <div />;
  154. }
  155. }
  156. var container = document.createElement('div');
  157. var instance = ReactDOM.render(<Component />, container);
  158. // No longer a public API, but we can test that it works internally by
  159. // reaching into the updater.
  160. expect(instance.isMounted()).toBe(true);
  161. ReactDOM.unmountComponentAtNode(container);
  162. // expect(instance.isMounted()).toBe(false);
  163. });
  164. it('warns if findDOMNode is used inside render', () => {
  165. class Component extends React.Component {
  166. state = {isMounted: false};
  167. componentDidMount() {
  168. this.setState({isMounted: true});
  169. }
  170. render() {
  171. if (this.state.isMounted) {
  172. expect(ReactDOM.findDOMNode(this).tagName).toBe('DIV');
  173. }
  174. return <div />;
  175. }
  176. }
  177. ReactTestUtils.renderIntoDocument(<Component />);
  178. });
  179. it('should carry through each of the phases of setup', () => {
  180. var clone = function(o) {
  181. return JSON.parse(JSON.stringify(o));
  182. };
  183. var GET_INIT_STATE_RETURN_VAL = {
  184. hasWillMountCompleted: false,
  185. hasRenderCompleted: false,
  186. hasDidMountCompleted: false,
  187. hasWillUnmountCompleted: false,
  188. };
  189. var INIT_RENDER_STATE = {
  190. hasWillMountCompleted: true,
  191. hasRenderCompleted: false,
  192. hasDidMountCompleted: false,
  193. hasWillUnmountCompleted: false,
  194. };
  195. var DID_MOUNT_STATE = {
  196. hasWillMountCompleted: true,
  197. hasRenderCompleted: true,
  198. hasDidMountCompleted: false,
  199. hasWillUnmountCompleted: false,
  200. };
  201. var NEXT_RENDER_STATE = {
  202. hasWillMountCompleted: true,
  203. hasRenderCompleted: true,
  204. hasDidMountCompleted: true,
  205. hasWillUnmountCompleted: false,
  206. };
  207. var WILL_UNMOUNT_STATE = {
  208. hasWillMountCompleted: true,
  209. hasDidMountCompleted: true,
  210. hasRenderCompleted: true,
  211. hasWillUnmountCompleted: false,
  212. };
  213. var POST_WILL_UNMOUNT_STATE = {
  214. hasWillMountCompleted: true,
  215. hasDidMountCompleted: true,
  216. hasRenderCompleted: true,
  217. hasWillUnmountCompleted: true,
  218. };
  219. function getLifeCycleState(instance) {
  220. return instance.isMounted() ? 'MOUNTED' : 'UNMOUNTED';
  221. }
  222. spyOn(console, 'error');
  223. class LifeCycleComponent extends React.Component {
  224. constructor(props, context) {
  225. super(props, context);
  226. this._testJournal = {};
  227. var initState = {
  228. hasWillMountCompleted: false,
  229. hasDidMountCompleted: false,
  230. hasRenderCompleted: false,
  231. hasWillUnmountCompleted: false,
  232. };
  233. this._testJournal.returnedFromGetInitialState = clone(initState);
  234. this._testJournal.lifeCycleAtStartOfGetInitialState = getLifeCycleState(
  235. this,
  236. );
  237. this.state = initState;
  238. }
  239. componentWillMount() {
  240. this._testJournal.stateAtStartOfWillMount = clone(this.state);
  241. this._testJournal.lifeCycleAtStartOfWillMount = getLifeCycleState(this);
  242. this.state.hasWillMountCompleted = true;
  243. }
  244. componentDidMount() {
  245. this._testJournal.stateAtStartOfDidMount = clone(this.state);
  246. this._testJournal.lifeCycleAtStartOfDidMount = getLifeCycleState(this);
  247. this.setState({hasDidMountCompleted: true});
  248. }
  249. render() {
  250. var isInitialRender = !this.state.hasRenderCompleted;
  251. if (isInitialRender) {
  252. this._testJournal.stateInInitialRender = clone(this.state);
  253. this._testJournal.lifeCycleInInitialRender = getLifeCycleState(this);
  254. } else {
  255. this._testJournal.stateInLaterRender = clone(this.state);
  256. this._testJournal.lifeCycleInLaterRender = getLifeCycleState(this);
  257. }
  258. // you would *NEVER* do anything like this in real code!
  259. this.state.hasRenderCompleted = true;
  260. return (
  261. <div ref="theDiv">
  262. I am the inner DIV
  263. </div>
  264. );
  265. }
  266. componentWillUnmount() {
  267. this._testJournal.stateAtStartOfWillUnmount = clone(this.state);
  268. this._testJournal.lifeCycleAtStartOfWillUnmount = getLifeCycleState(
  269. this,
  270. );
  271. this.state.hasWillUnmountCompleted = true;
  272. }
  273. }
  274. // A component that is merely "constructed" (as in "constructor") but not
  275. // yet initialized, or rendered.
  276. //
  277. var container = document.createElement('div');
  278. var instance = ReactDOM.render(<LifeCycleComponent />, container);
  279. // getInitialState
  280. expect(instance._testJournal.returnedFromGetInitialState).toEqual(
  281. GET_INIT_STATE_RETURN_VAL,
  282. );
  283. expect(instance._testJournal.lifeCycleAtStartOfGetInitialState).toBe(
  284. 'UNMOUNTED',
  285. );
  286. // componentWillMount
  287. expect(instance._testJournal.stateAtStartOfWillMount).toEqual(
  288. instance._testJournal.returnedFromGetInitialState,
  289. );
  290. expect(instance._testJournal.lifeCycleAtStartOfWillMount).toBe('UNMOUNTED');
  291. // componentDidMount
  292. expect(instance._testJournal.stateAtStartOfDidMount).toEqual(
  293. DID_MOUNT_STATE,
  294. );
  295. expect(instance._testJournal.lifeCycleAtStartOfDidMount).toBe('MOUNTED');
  296. // initial render
  297. expect(instance._testJournal.stateInInitialRender).toEqual(
  298. INIT_RENDER_STATE,
  299. );
  300. expect(instance._testJournal.lifeCycleInInitialRender).toBe('UNMOUNTED');
  301. expect(getLifeCycleState(instance)).toBe('MOUNTED');
  302. // Now *update the component*
  303. instance.forceUpdate();
  304. // render 2nd time
  305. expect(instance._testJournal.stateInLaterRender).toEqual(NEXT_RENDER_STATE);
  306. expect(instance._testJournal.lifeCycleInLaterRender).toBe('MOUNTED');
  307. expect(getLifeCycleState(instance)).toBe('MOUNTED');
  308. ReactDOM.unmountComponentAtNode(container);
  309. expect(instance._testJournal.stateAtStartOfWillUnmount).toEqual(
  310. WILL_UNMOUNT_STATE,
  311. );
  312. // componentWillUnmount called right before unmount.
  313. expect(instance._testJournal.lifeCycleAtStartOfWillUnmount).toBe('MOUNTED');
  314. // But the current lifecycle of the component is unmounted.
  315. /*expect(getLifeCycleState(instance)).toBe('UNMOUNTED');
  316. expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);*/
  317. });
  318. it('should allow state updates in componentDidMount', () => {
  319. /**
  320. * calls setState in an componentDidMount.
  321. */
  322. class SetStateInComponentDidMount extends React.Component {
  323. state = {
  324. stateField: this.props.valueToUseInitially,
  325. };
  326. componentDidMount() {
  327. this.setState({stateField: this.props.valueToUseInOnDOMReady});
  328. }
  329. render() {
  330. return <div />;
  331. }
  332. }
  333. var instance = (
  334. <SetStateInComponentDidMount
  335. valueToUseInitially="hello"
  336. valueToUseInOnDOMReady="goodbye"
  337. />
  338. );
  339. instance = ReactTestUtils.renderIntoDocument(instance);
  340. expect(instance.state.stateField).toBe('goodbye');
  341. });
  342. it('should call nested lifecycle methods in the right order', () => {
  343. var log;
  344. var logger = function(msg) {
  345. return function() {
  346. // return true for shouldComponentUpdate
  347. log.push(msg);
  348. return true;
  349. };
  350. };
  351. class Outer extends React.Component {
  352. componentWillMount = logger('outer componentWillMount');
  353. componentDidMount = logger('outer componentDidMount');
  354. componentWillReceiveProps = logger('outer componentWillReceiveProps');
  355. shouldComponentUpdate = logger('outer shouldComponentUpdate');
  356. componentWillUpdate = logger('outer componentWillUpdate');
  357. componentDidUpdate = logger('outer componentDidUpdate');
  358. componentWillUnmount = logger('outer componentWillUnmount');
  359. render() {
  360. return <div><Inner x={this.props.x} /></div>;
  361. }
  362. }
  363. class Inner extends React.Component {
  364. componentWillMount = logger('inner componentWillMount');
  365. componentDidMount = logger('inner componentDidMount');
  366. componentWillReceiveProps = logger('inner componentWillReceiveProps');
  367. shouldComponentUpdate = logger('inner shouldComponentUpdate');
  368. componentWillUpdate = logger('inner componentWillUpdate');
  369. componentDidUpdate = logger('inner componentDidUpdate');
  370. componentWillUnmount = logger('inner componentWillUnmount');
  371. render() {
  372. return <span>{this.props.x}</span>;
  373. }
  374. }
  375. var container = document.createElement('div');
  376. log = [];
  377. ReactDOM.render(<Outer x={17} />, container);
  378. expect(log).toEqual([
  379. 'outer componentWillMount',
  380. 'inner componentWillMount',
  381. 'inner componentDidMount',
  382. 'outer componentDidMount',
  383. ]);
  384. log = [];
  385. ReactDOM.render(<Outer x={42} />, container);
  386. expect(log).toEqual([
  387. 'outer componentWillReceiveProps',
  388. 'outer shouldComponentUpdate',
  389. 'outer componentWillUpdate',
  390. 'inner componentWillReceiveProps',
  391. 'inner shouldComponentUpdate',
  392. 'inner componentWillUpdate',
  393. 'inner componentDidUpdate',
  394. 'outer componentDidUpdate',
  395. ]);
  396. log = [];
  397. ReactDOM.unmountComponentAtNode(container);
  398. expect(log).toEqual([
  399. 'outer componentWillUnmount',
  400. 'inner componentWillUnmount',
  401. ]);
  402. });
  403. //暂不支持这种形式的component
  404. /*it('calls effects on module-pattern component', function() {
  405. const log = [];
  406. function Parent() {
  407. return {
  408. render() {
  409. expect(typeof this.props).toBe('object');
  410. log.push('render');
  411. return <Child />;
  412. },
  413. componentWillMount() {
  414. log.push('will mount');
  415. },
  416. componentDidMount() {
  417. log.push('did mount');
  418. },
  419. componentDidUpdate() {
  420. log.push('did update');
  421. },
  422. getChildContext() {
  423. return {x: 2};
  424. },
  425. };
  426. }
  427. Parent.childContextTypes = {
  428. x: PropTypes.number,
  429. };
  430. function Child(props, context) {
  431. expect(context.x).toBe(2);
  432. return <div />;
  433. }
  434. Child.contextTypes = {
  435. x: PropTypes.number,
  436. };
  437. const div = document.createElement('div');
  438. ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
  439. ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
  440. expect(log).toEqual([
  441. 'will mount',
  442. 'render',
  443. 'did mount',
  444. 'ref',
  445. 'render',
  446. 'did update',
  447. 'ref',
  448. ]);
  449. });*/
  450. });