ReactCompositeComponent-test.jsx 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317
  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. import {shallowCompare} from '../../src/PureComponent';
  6. var ReactDOM = {
  7. render: render,
  8. unmountComponentAtNode: unmountComponentAtNode,
  9. findDOMNode: findDOMNode
  10. }
  11. React.render = render;
  12. // import React from "../../dist/ReactANU";
  13. // var ReactDOM = React;
  14. // expect(childInstance.context).toEqual({ foo: "bar", depth: 0 }); sk
  15. //https://github.com/facebook/react/blob/master/src/isomorphic/children/__tests__/ReactChildren-test.js
  16. describe("ReactCompositeComponent", function() {
  17. // this.timeout(200000);
  18. it("should support module pattern components", () => {
  19. function Child({ test }) {
  20. return {
  21. render() {
  22. return <div>{test}</div>;
  23. }
  24. };
  25. }
  26. var el = document.createElement("div");
  27. ReactDOM.render(<Child test="test" />, el);
  28. expect(el.textContent).toBe("test");
  29. });
  30. it("should support rendering to different child types over time", () => {
  31. var instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
  32. var el = ReactDOM.findDOMNode(instance);
  33. expect(el.tagName).toBe("A");
  34. instance._toggleActivatedState();
  35. el = ReactDOM.findDOMNode(instance);
  36. expect(el.tagName).toBe("B");
  37. instance._toggleActivatedState();
  38. el = ReactDOM.findDOMNode(instance);
  39. expect(el.tagName).toBe("A");
  40. });
  41. var MorphingComponent = class extends React.Component {
  42. state = { activated: false };
  43. _toggleActivatedState = () => {
  44. this.setState({ activated: !this.state.activated });
  45. };
  46. render() {
  47. var toggleActivatedState = this._toggleActivatedState;
  48. return !this.state.activated ? <a ref="x" onClick={toggleActivatedState} /> : <b ref="x" onClick={toggleActivatedState} />;
  49. }
  50. };
  51. it("should react to state changes from callbacks", () => {
  52. var instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
  53. var el = ReactDOM.findDOMNode(instance);
  54. expect(el.tagName).toBe("A");
  55. ReactTestUtils.Simulate.click(el);
  56. el = ReactDOM.findDOMNode(instance);
  57. expect(el.tagName).toBe("B");
  58. });
  59. it("should rewire refs when rendering to different child types", () => {
  60. var instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
  61. expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe("A");
  62. instance._toggleActivatedState();
  63. expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe("B");
  64. instance._toggleActivatedState();
  65. expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe("A");
  66. });
  67. var ChildUpdates = class extends React.Component {
  68. getAnchor = () => {
  69. return this.refs.anch;
  70. };
  71. render() {
  72. var className = this.props.anchorClassOn ? "anchorClass" : "";
  73. return this.props.renderAnchor ? <a ref="anch" className={className} /> : <b />;
  74. }
  75. };
  76. it("should not cache old DOM nodes when switching constructors", () => {
  77. var container = document.createElement("div");
  78. var instance = ReactDOM.render(<ChildUpdates renderAnchor={true} anchorClassOn={false} />, container);
  79. ReactDOM.render(
  80. // Warm any cache
  81. <ChildUpdates renderAnchor={true} anchorClassOn={true} />,
  82. container
  83. );
  84. ReactDOM.render(
  85. // Clear out the anchor
  86. <ChildUpdates renderAnchor={false} anchorClassOn={true} />,
  87. container
  88. );
  89. ReactDOM.render(
  90. // rerender
  91. <ChildUpdates renderAnchor={true} anchorClassOn={false} />,
  92. container
  93. );
  94. expect(instance.getAnchor().className).toBe("");
  95. });
  96. it("should use default values for undefined props", () => {
  97. class Component extends React.Component {
  98. static defaultProps = { prop: "testKey" };
  99. render() {
  100. return <span />;
  101. }
  102. }
  103. var instance1 = ReactTestUtils.renderIntoDocument(<Component />);
  104. expect(instance1.props).toEqual({ prop: "testKey" });
  105. var instance2 = ReactTestUtils.renderIntoDocument(<Component prop={undefined} />);
  106. expect(instance2.props).toEqual({ prop: "testKey" });
  107. var instance3 = ReactTestUtils.renderIntoDocument(<Component prop={null} />);
  108. expect(instance3.props).toEqual({ prop: null });
  109. });
  110. it("should not mutate passed-in props object", () => {
  111. class Component extends React.Component {
  112. static defaultProps = { prop: "testKey" };
  113. render() {
  114. return <span />;
  115. }
  116. }
  117. var inputProps = {};
  118. var instance1 = <Component {...inputProps} />;
  119. instance1 = ReactTestUtils.renderIntoDocument(instance1);
  120. expect(instance1.props.prop).toBe("testKey");
  121. // We don't mutate the input, just in case the caller wants to do something
  122. // with it after using it to instantiate a component
  123. expect(inputProps.prop).toBe(void 666);
  124. });
  125. it("should warn about `forceUpdate` on unmounted components", () => {
  126. var container = document.createElement("div");
  127. document.body.appendChild(container);
  128. class Component extends React.Component {
  129. render() {
  130. return <div />;
  131. }
  132. }
  133. var instance = <Component />;
  134. expect(instance.forceUpdate).toBe(void 666);
  135. instance = ReactDOM.render(instance, container);
  136. instance.forceUpdate();
  137. ReactDOM.unmountComponentAtNode(container);
  138. instance.forceUpdate();
  139. });
  140. it("should warn about `setState` on unmounted components", () => {
  141. var container = document.createElement("div");
  142. document.body.appendChild(container);
  143. var renders = 0;
  144. class Component extends React.Component {
  145. state = { value: 0 };
  146. render() {
  147. renders++;
  148. return <div />;
  149. }
  150. }
  151. var instance = <Component />;
  152. expect(instance.setState).toBe(void 666);
  153. instance = ReactDOM.render(instance, container);
  154. expect(renders).toBe(1);
  155. instance.setState({ value: 1 });
  156. expect(renders).toBe(2);
  157. ReactDOM.unmountComponentAtNode(container);
  158. instance.setState({ value: 2 });
  159. expect(renders).toBe(2);
  160. });
  161. it("should silently allow `setState`, not call cb on unmounting components", () => {
  162. var cbCalled = false;
  163. var container = document.createElement("div");
  164. document.body.appendChild(container);
  165. class Component extends React.Component {
  166. state = { value: 0 };
  167. componentWillUnmount() {
  168. expect(() => {
  169. this.setState({ value: 2 }, function() {
  170. cbCalled = true;
  171. });
  172. }).not.toThrow();
  173. }
  174. render() {
  175. return <div />;
  176. }
  177. }
  178. var instance = ReactDOM.render(<Component />, container);
  179. instance.setState({ value: 1 });
  180. ReactDOM.unmountComponentAtNode(container);
  181. expect(cbCalled).toBe(false);
  182. });
  183. it("should warn about `setState` in render", () => {
  184. spyOn(console, "error");
  185. var container = document.createElement("div");
  186. var renderedState = -1;
  187. var renderPasses = 0;
  188. class Component extends React.Component {
  189. state = { value: 0 };
  190. render() {
  191. renderPasses++;
  192. renderedState = this.state.value;
  193. if (this.state.value === 0) {
  194. this.setState({ value: 1 });
  195. }
  196. return <div />;
  197. }
  198. }
  199. var instance = ReactDOM.render(<Component />, container);
  200. // The setState call is queued and then executed as a second pass. This
  201. // behavior is undefined though so we're free to change it to suit the
  202. // implementation details.
  203. expect(renderPasses).toBe(2);
  204. expect(renderedState).toBe(1);
  205. expect(instance.state.value).toBe(1);
  206. // Forcing a rerender anywhere will cause the update to happen.
  207. var instance2 = ReactDOM.render(<Component prop={123} />, container);
  208. expect(instance).toBe(instance2);
  209. expect(renderedState).toBe(1);
  210. expect(instance2.state.value).toBe(1);
  211. });
  212. it("should warn about `setState` in getChildContext", () => {
  213. spyOn(console, "error");
  214. var container = document.createElement("div");
  215. var renderPasses = 0;
  216. class Component extends React.Component {
  217. state = { value: 0 };
  218. getChildContext() {
  219. if (this.state.value === 0) {
  220. this.setState({ value: 4 });
  221. }
  222. }
  223. render() {
  224. renderPasses++;
  225. return <div />;
  226. }
  227. }
  228. Component.childContextTypes = {};
  229. var instance = ReactDOM.render(<Component />, container);
  230. expect(renderPasses).toBe(2);
  231. expect(instance.state.value).toBe(4);
  232. });
  233. it("should call componentWillUnmount before unmounting", () => {
  234. var container = document.createElement("div");
  235. var innerUnmounted = false;
  236. class Component extends React.Component {
  237. render() {
  238. return (
  239. <div>
  240. <Inner />
  241. Text
  242. </div>
  243. );
  244. }
  245. }
  246. class Inner extends React.Component {
  247. componentWillUnmount() {
  248. innerUnmounted = true;
  249. }
  250. render() {
  251. return <div />;
  252. }
  253. }
  254. ReactDOM.render(<Component />, container);
  255. ReactDOM.unmountComponentAtNode(container);
  256. expect(innerUnmounted).toBe(true);
  257. });
  258. it("should warn when shouldComponentUpdate() returns undefined", () => {
  259. var container = document.createElement("div");
  260. class Component extends React.Component {
  261. state = { bogus: false };
  262. shouldComponentUpdate() {
  263. return undefined;
  264. }
  265. render() {
  266. return <div>{this.state.bogus}</div>;
  267. }
  268. }
  269. var instance = ReactDOM.render(<Component />, container);
  270. instance.setState({ bogus: true });
  271. expect(container.textContent).toBe(""); //布尔会转换为空字符串
  272. });
  273. //https://github.com/facebook/react/blob/master/src/renderers/__tests__/ReactCompositeComponent-test.js#L526
  274. it("should pass context to children when not owner", () => {
  275. class Parent extends React.Component {
  276. render() {
  277. return (
  278. <Child>
  279. <Grandchild />
  280. </Child>
  281. );
  282. }
  283. }
  284. class Child extends React.Component {
  285. static childContextTypes = {
  286. foo: PropTypes.string
  287. };
  288. getChildContext() {
  289. return {
  290. foo: "bar"
  291. };
  292. }
  293. render() {
  294. return React.Children.only(this.props.children);
  295. }
  296. }
  297. class Grandchild extends React.Component {
  298. static contextTypes = {
  299. foo: PropTypes.string
  300. };
  301. render() {
  302. return <div>{this.context.foo}</div>;
  303. }
  304. }
  305. var component = ReactTestUtils.renderIntoDocument(<Parent />);
  306. expect(ReactDOM.findDOMNode(component).innerHTML).toBe("bar");
  307. });
  308. it("should skip update when rerendering element in container", () => {
  309. class Parent extends React.Component {
  310. render() {
  311. return <div>{this.props.children}</div>;
  312. }
  313. }
  314. var childRenders = 0;
  315. class Child extends React.Component {
  316. render() {
  317. childRenders++;
  318. return <div />;
  319. }
  320. }
  321. var container = document.createElement("div");
  322. var child = <Child />;
  323. ReactDOM.render(<Parent>{child}</Parent>, container);
  324. ReactDOM.render(<Parent>{child}</Parent>, container);
  325. expect(childRenders).toBe(1);
  326. });
  327. //context穿透更新
  328. it("should pass context when re-rendered for static child", () => {
  329. var parentInstance = null;
  330. var childInstance = null;
  331. class Parent extends React.Component {
  332. static childContextTypes = {
  333. foo: PropTypes.string,
  334. flag: PropTypes.bool
  335. };
  336. state = {
  337. flag: false
  338. };
  339. getChildContext() {
  340. return {
  341. foo: "bar",
  342. flag: this.state.flag
  343. };
  344. }
  345. render() {
  346. return React.Children.only(this.props.children);
  347. }
  348. }
  349. class Middle extends React.Component {
  350. render() {
  351. return this.props.children;
  352. }
  353. }
  354. class Child extends React.Component {
  355. static contextTypes = {
  356. foo: PropTypes.string,
  357. flag: PropTypes.bool
  358. };
  359. render() {
  360. childInstance = this;
  361. return <span>Child</span>;
  362. }
  363. }
  364. parentInstance = ReactTestUtils.renderIntoDocument(
  365. <Parent>
  366. <Middle>
  367. <Child />
  368. </Middle>
  369. </Parent>
  370. );
  371. expect(parentInstance.state.flag).toBe(false);
  372. expect(childInstance.context).toEqual({ foo: "bar", flag: false });
  373. parentInstance.setState({ flag: true });
  374. expect(parentInstance.state.flag).toBe(true);
  375. expect(childInstance.context).toEqual({ foo: "bar", flag: true });
  376. });
  377. //context穿透更新
  378. it("should pass context when re-rendered for static child within a composite component", () => {
  379. class Parent extends React.Component {
  380. static childContextTypes = {
  381. flag: PropTypes.bool
  382. };
  383. state = {
  384. flag: true
  385. };
  386. getChildContext() {
  387. return {
  388. flag: this.state.flag
  389. };
  390. }
  391. render() {
  392. return <div>{this.props.children}</div>;
  393. }
  394. }
  395. class Child extends React.Component {
  396. static contextTypes = {
  397. flag: PropTypes.bool
  398. };
  399. render() {
  400. return <div />;
  401. }
  402. }
  403. class Wrapper extends React.Component {
  404. render() {
  405. return (
  406. <Parent ref="parent">
  407. <Child ref="child" />
  408. </Parent>
  409. );
  410. }
  411. }
  412. var wrapper = ReactTestUtils.renderIntoDocument(<Wrapper />);
  413. expect(wrapper.refs.parent.state.flag).toEqual(true);
  414. expect(wrapper.refs.child.context).toEqual({ flag: true });
  415. // We update <Parent /> while <Child /> is still a static prop relative to this update
  416. wrapper.refs.parent.setState({ flag: false });
  417. expect(wrapper.refs.parent.state.flag).toEqual(false);
  418. expect(wrapper.refs.child.context).toEqual({ flag: false });
  419. });
  420. it("should pass context transitively", () => {
  421. var childInstance = null;
  422. var grandchildInstance = null;
  423. class Parent extends React.Component {
  424. static childContextTypes = {
  425. foo: PropTypes.string,
  426. depth: PropTypes.number
  427. };
  428. getChildContext() {
  429. return {
  430. foo: "bar",
  431. depth: 0
  432. };
  433. }
  434. render() {
  435. return <Child />;
  436. }
  437. }
  438. class Child extends React.Component {
  439. static contextTypes = {
  440. foo: PropTypes.string,
  441. depth: PropTypes.number
  442. };
  443. static childContextTypes = {
  444. depth: PropTypes.number
  445. };
  446. getChildContext() {
  447. return {
  448. depth: this.context.depth + 1
  449. };
  450. }
  451. render() {
  452. childInstance = this;
  453. return <Grandchild />;
  454. }
  455. }
  456. class Grandchild extends React.Component {
  457. static contextTypes = {
  458. foo: PropTypes.string,
  459. depth: PropTypes.number
  460. };
  461. render() {
  462. grandchildInstance = this;
  463. return <div />;
  464. }
  465. }
  466. ReactTestUtils.renderIntoDocument(<Parent />);
  467. // expect(childInstance.context).toEqual({ foo: "bar", depth: 0 });
  468. expect(grandchildInstance.context).toEqual({ foo: "bar", depth: 1 });
  469. });
  470. it("should pass context when re-rendered", () => {
  471. var parentInstance = null;
  472. var childInstance = null;
  473. class Parent extends React.Component {
  474. static childContextTypes = {
  475. foo: PropTypes.string,
  476. depth: PropTypes.number
  477. };
  478. state = {
  479. flag: false
  480. };
  481. getChildContext() {
  482. return {
  483. foo: "bar",
  484. depth: 0
  485. };
  486. }
  487. render() {
  488. var output = <Child />;
  489. if (!this.state.flag) {
  490. output = <span>Child</span>;
  491. }
  492. return output;
  493. }
  494. }
  495. class Child extends React.Component {
  496. static contextTypes = {
  497. foo: PropTypes.string,
  498. depth: PropTypes.number
  499. };
  500. render() {
  501. childInstance = this;
  502. return <span>Child</span>;
  503. }
  504. }
  505. parentInstance = ReactTestUtils.renderIntoDocument(<Parent />);
  506. expect(childInstance).toBeNull();
  507. expect(parentInstance.state.flag).toBe(false);
  508. // ReactDOM.unstable_batchedUpdates(function() {
  509. // parentInstance.setState({flag: true});
  510. // });
  511. // expect(parentInstance.state.flag).toBe(true);
  512. // expect(childInstance.context).toEqual({foo: 'bar', depth: 0});
  513. });
  514. it("unmasked context propagates through updates", () => {
  515. class Leaf extends React.Component {
  516. static contextTypes = {
  517. foo: PropTypes.string.isRequired
  518. };
  519. componentWillReceiveProps(nextProps, nextContext) {
  520. expect("foo" in nextContext).toBe(true);
  521. }
  522. shouldComponentUpdate(nextProps, nextState, nextContext) {
  523. expect("foo" in nextContext).toBe(true);
  524. return true;
  525. }
  526. render() {
  527. return <span>{this.context.foo}</span>;
  528. }
  529. }
  530. class Intermediary extends React.Component {
  531. componentWillReceiveProps(nextProps, nextContext) {
  532. expect("foo" in nextContext).toBe(false);
  533. }
  534. shouldComponentUpdate(nextProps, nextState, nextContext) {
  535. expect("foo" in nextContext).toBe(false);
  536. return true;
  537. }
  538. render() {
  539. return <Leaf />;
  540. }
  541. }
  542. class Parent extends React.Component {
  543. static childContextTypes = {
  544. foo: PropTypes.string
  545. };
  546. getChildContext() {
  547. return {
  548. foo: this.props.cntxt
  549. };
  550. }
  551. render() {
  552. return <Intermediary />;
  553. }
  554. }
  555. var div = document.createElement("div");
  556. ReactDOM.render(<Parent cntxt="noise" />, div);
  557. expect(div.children[0].innerHTML).toBe("noise");
  558. // div.children[0].innerHTML = "aliens";
  559. // div.children[0].id = "aliens";
  560. // expect(div.children[0].innerHTML).toBe("aliens");
  561. // expect(div.children[0].id).toBe("aliens");
  562. ReactDOM.render(<Parent cntxt="bar" />, div);
  563. expect(div.children[0].innerHTML).toBe("bar");
  564. // expect(div.children[0].id).toBe("aliens");
  565. });
  566. it("should trigger componentWillReceiveProps for context changes", () => {
  567. var contextChanges = 0;
  568. var propChanges = 0;
  569. class GrandChild extends React.Component {
  570. static contextTypes = {
  571. foo: PropTypes.string.isRequired
  572. };
  573. componentWillReceiveProps(nextProps, nextContext) {
  574. expect("foo" in nextContext).toBe(true);
  575. if (nextProps !== this.props) {
  576. propChanges++;
  577. }
  578. if (nextContext !== this.context) {
  579. contextChanges++;
  580. }
  581. }
  582. render() {
  583. return <span className="grand-child">{this.props.children}</span>;
  584. }
  585. }
  586. class ChildWithContext extends React.Component {
  587. static contextTypes = {
  588. foo: PropTypes.string.isRequired
  589. };
  590. componentWillReceiveProps(nextProps, nextContext) {
  591. expect("foo" in nextContext).toBe(true);
  592. if (nextProps !== this.props) {
  593. propChanges++;
  594. }
  595. if (nextContext !== this.context) {
  596. contextChanges++;
  597. }
  598. }
  599. render() {
  600. return <div className="child-with">{this.props.children}</div>;
  601. }
  602. }
  603. class ChildWithoutContext extends React.Component {
  604. componentWillReceiveProps(nextProps, nextContext) {
  605. //child未变 应当不执行componentWillReceiveProps
  606. // expect("foo" in nextContext).toBe(false);
  607. if (nextProps !== this.props) {
  608. propChanges++;
  609. }
  610. if (nextContext !== this.context) {
  611. contextChanges++;
  612. }
  613. }
  614. render() {
  615. return <div className="child-without">{this.props.children}</div>;
  616. }
  617. }
  618. class Parent extends React.Component {
  619. static childContextTypes = {
  620. foo: PropTypes.string
  621. };
  622. state = {
  623. foo: "abc"
  624. };
  625. getChildContext() {
  626. return {
  627. foo: this.state.foo
  628. };
  629. }
  630. render() {
  631. return <div className="parent">{this.props.children}</div>;
  632. }
  633. }
  634. var div = document.createElement("div");
  635. var parentInstance = null;
  636. ReactDOM.render(
  637. <Parent ref={inst => (parentInstance = inst)}>
  638. <ChildWithoutContext>
  639. A1
  640. <GrandChild>A2</GrandChild>
  641. </ChildWithoutContext>
  642. <ChildWithContext>
  643. B1
  644. <GrandChild>B2</GrandChild>
  645. </ChildWithContext>
  646. </Parent>,
  647. div
  648. );
  649. parentInstance.setState({
  650. foo: "def"
  651. });
  652. expect(propChanges).toBe(0);
  653. expect(contextChanges).toBe(3); // ChildWithContext, GrandChild x 2
  654. });
  655. it("only renders once if updated in componentWillReceiveProps", () => {
  656. var renders = 0;
  657. class Component extends React.Component {
  658. state = { updated: false };
  659. componentWillReceiveProps(props) {
  660. expect(props.update).toBe(1);
  661. expect(renders).toBe(1);
  662. this.setState({ updated: true });
  663. expect(renders).toBe(1);
  664. }
  665. render() {
  666. renders++;
  667. return <div />;
  668. }
  669. }
  670. var container = document.createElement("div");
  671. var instance = ReactDOM.render(<Component update={0} />, container);
  672. expect(renders).toBe(1);
  673. expect(instance.state.updated).toBe(false);
  674. ReactDOM.render(<Component update={1} />, container);
  675. expect(renders).toBe(2);
  676. expect(instance.state.updated).toBe(true);
  677. });
  678. it("only renders once if updated in componentWillReceiveProps when batching", () => {
  679. var renders = 0;
  680. class Component extends React.Component {
  681. state = { updated: false };
  682. componentWillReceiveProps(props) {
  683. expect(props.update).toBe(1);
  684. expect(renders).toBe(1);
  685. this.setState({ updated: true });
  686. expect(renders).toBe(1);
  687. }
  688. render() {
  689. renders++;
  690. return <div />;
  691. }
  692. }
  693. var container = document.createElement("div");
  694. var instance = ReactDOM.render(<Component update={0} />, container);
  695. expect(renders).toBe(1);
  696. expect(instance.state.updated).toBe(false);
  697. // ReactDOM.unstable_batchedUpdates(() => {
  698. // ReactDOM.render(<Component update={1} />, container);
  699. // });
  700. // expect(renders).toBe(2);
  701. // expect(instance.state.updated).toBe(true);
  702. });
  703. it("should update refs if shouldComponentUpdate gives false", () => {
  704. class Static extends React.Component {
  705. shouldComponentUpdate() {
  706. return false;
  707. }
  708. render() {
  709. return <div>{this.props.children}</div>;
  710. }
  711. }
  712. class Component extends React.Component {
  713. render() {
  714. if (this.props.flipped) {
  715. return (
  716. <div>
  717. <Static ref="static0" key="B">
  718. B (ignored)
  719. </Static>
  720. <Static ref="static1" key="A">
  721. A (ignored)
  722. </Static>
  723. </div>
  724. );
  725. } else {
  726. return (
  727. <div>
  728. <Static ref="static0" key="A">
  729. A
  730. </Static>
  731. <Static ref="static1" key="B">
  732. B
  733. </Static>
  734. </div>
  735. );
  736. }
  737. }
  738. }
  739. var container = document.createElement("div");
  740. var comp = ReactDOM.render(<Component flipped={false} />, container);
  741. //keyA <> instance0 <> static0 <> contentA
  742. //keyB <> instance1 <> static1 <> contentB
  743. expect(ReactDOM.findDOMNode(comp.refs.static0).textContent).toBe("A");
  744. expect(ReactDOM.findDOMNode(comp.refs.static1).textContent).toBe("B");
  745. //keyA <> instance0 <> static1 <> contentA
  746. //keyB <> instance1 <> static1 <> contentB
  747. // When flipping the order, the refs should update even though the actual
  748. // contents do not
  749. ReactDOM.render(<Component flipped={true} />, container);
  750. // expect(ReactDOM.findDOMNode(comp.refs.static0).textContent).toBe("B");
  751. // expect(ReactDOM.findDOMNode(comp.refs.static1).textContent).toBe("A");
  752. });
  753. it("should allow access to findDOMNode in componentWillUnmount", () => {
  754. var a = null;
  755. var b = null;
  756. class Component extends React.Component {
  757. componentDidMount() {
  758. a = ReactDOM.findDOMNode(this);
  759. expect(a).not.toBe(null);
  760. }
  761. componentWillUnmount() {
  762. b = ReactDOM.findDOMNode(this);
  763. expect(b).not.toBe(null);
  764. }
  765. render() {
  766. return <div />;
  767. }
  768. }
  769. var container = document.createElement("div");
  770. expect(a).toBe(container.firstChild);
  771. ReactDOM.render(<Component />, container);
  772. ReactDOM.unmountComponentAtNode(container);
  773. expect(a).toBe(b);
  774. });
  775. it("context should be passed down from the parent", () => {
  776. class Parent extends React.Component {
  777. static childContextTypes = {
  778. foo: PropTypes.string
  779. };
  780. getChildContext() {
  781. return {
  782. foo: "bar"
  783. };
  784. }
  785. render() {
  786. return <div>{this.props.children}</div>;
  787. }
  788. }
  789. class Component extends React.Component {
  790. static contextTypes = {
  791. foo: PropTypes.string.isRequired
  792. };
  793. render() {
  794. return <div />;
  795. }
  796. }
  797. var div = document.createElement("div");
  798. ReactDOM.render(
  799. <Parent>
  800. <Component />
  801. </Parent>,
  802. div
  803. );
  804. });
  805. it("should replace state", () => {
  806. class Moo extends React.Component {
  807. state = { x: 1 };
  808. render() {
  809. return <div />;
  810. }
  811. }
  812. var moo = ReactTestUtils.renderIntoDocument(<Moo />);
  813. // No longer a public API, but we can test that it works internally by
  814. // reaching into the updater.
  815. // moo.updater.enqueueReplaceState(moo, {y: 2});
  816. // expect('x' in moo.state).toBe(false);
  817. expect(moo.state.y).toBe(void 666);
  818. });
  819. it("should support objects with prototypes as state", () => {
  820. var NotActuallyImmutable = function(str) {
  821. this.str = str;
  822. };
  823. NotActuallyImmutable.prototype.amIImmutable = function() {
  824. return true;
  825. };
  826. class Moo extends React.Component {
  827. state = new NotActuallyImmutable("first");
  828. // No longer a public API, but we can test that it works internally by
  829. // reaching into the updater.
  830. _replaceState = function(a) {
  831. this.state = a;
  832. this.forceUpdate();
  833. };
  834. render() {
  835. return <div />;
  836. }
  837. }
  838. var moo = ReactTestUtils.renderIntoDocument(<Moo />);
  839. expect(moo.state.str).toBe("first");
  840. expect(moo.state.amIImmutable()).toBe(true);
  841. var secondState = new NotActuallyImmutable("second");
  842. moo._replaceState(secondState);
  843. expect(moo.state.str).toBe("second");
  844. expect(moo.state.amIImmutable()).toBe(true);
  845. expect(moo.state).toBe(secondState);
  846. moo.setState({ str: "third" });
  847. expect(moo.state.str).toBe("third");
  848. // Here we lose the prototype.
  849. expect(moo.state.amIImmutable).toBe(undefined);
  850. });
  851. it("props对象不能在构造器里被重写", () => {
  852. var container = document.createElement("div");
  853. class Foo extends React.Component {
  854. constructor(props) {
  855. super(props);
  856. this.props = { idx: "xxx" };
  857. }
  858. render() {
  859. return <span>{this.props.idx}</span>;
  860. }
  861. }
  862. ReactDOM.render(<Foo idx="aaa" />, container);
  863. expect(container.textContent).toBe("aaa");
  864. });
  865. it("should warn when mutated props are passed", () => {
  866. var container = document.createElement("div");
  867. class Foo extends React.Component {
  868. constructor(props) {
  869. var _props = { idx: props.idx + "!" };
  870. super(_props);
  871. }
  872. render() {
  873. return <span>{this.props.idx}</span>;
  874. }
  875. }
  876. ReactDOM.render(<Foo idx="qwe" />, container);
  877. expect(container.textContent).toBe("qwe");
  878. });
  879. it("should only call componentWillUnmount once", () => {
  880. var app;
  881. var count = 0;
  882. class App extends React.Component {
  883. render() {
  884. if (this.props.stage === 1) {
  885. return <UnunmountableComponent />;
  886. } else {
  887. return null;
  888. }
  889. }
  890. }
  891. class UnunmountableComponent extends React.Component {
  892. componentWillUnmount() {
  893. app.setState({});
  894. count++;
  895. throw Error("always fails");
  896. }
  897. render() {
  898. return <div>Hello {this.props.name}</div>;
  899. }
  900. }
  901. var container = document.createElement("div");
  902. var setRef = ref => {
  903. if (ref) {
  904. app = ref;
  905. }
  906. };
  907. expect(function() {
  908. ReactDOM.render(<App ref={setRef} stage={1} />, container);
  909. ReactDOM.render(<App ref={setRef} stage={2} />, container);
  910. }).toThrow();
  911. expect(count).toBe(1);
  912. });
  913. it("prepares new child before unmounting old", () => {
  914. var log = [];
  915. class Spy extends React.Component {
  916. componentWillMount() {
  917. log.push(this.props.name + " componentWillMount");
  918. }
  919. render() {
  920. log.push(this.props.name + " render");
  921. return <div />;
  922. }
  923. componentDidMount() {
  924. log.push(this.props.name + " componentDidMount");
  925. }
  926. componentWillUnmount() {
  927. log.push(this.props.name + " componentWillUnmount");
  928. }
  929. }
  930. class Wrapper extends React.Component {
  931. render() {
  932. return <Spy key={this.props.name} name={this.props.name} />;
  933. }
  934. }
  935. var container = document.createElement("div");
  936. ReactDOM.render(<Wrapper name="A" />, container);
  937. ReactDOM.render(<Wrapper name="B" />, container);
  938. expect(log).toEqual(["A componentWillMount", "A render", "A componentDidMount", "A componentWillUnmount", "B componentWillMount", "B render", "B componentDidMount"]);
  939. });
  940. it("respects a shallow shouldComponentUpdate implementation", () => {
  941. var renderCalls = 0;
  942. class PlasticWrap extends React.Component {
  943. constructor(props, context) {
  944. super(props, context);
  945. this.state = {
  946. color: "green"
  947. };
  948. }
  949. render() {
  950. return <Apple color={this.state.color} ref="apple" />;
  951. }
  952. }
  953. class Apple extends React.Component {
  954. state = {
  955. cut: false,
  956. slices: 1
  957. };
  958. shouldComponentUpdate(nextProps, nextState) {
  959. return shallowCompare(this, nextProps, nextState);
  960. }
  961. cut() {
  962. this.setState({
  963. cut: true,
  964. slices: 10
  965. });
  966. }
  967. eatSlice() {
  968. this.setState({
  969. slices: this.state.slices - 1
  970. });
  971. }
  972. render() {
  973. renderCalls++;
  974. return <div />;
  975. }
  976. }
  977. var container = document.createElement("div");
  978. var instance = ReactDOM.render(<PlasticWrap />, container);
  979. expect(renderCalls).toBe(1);
  980. // Do not re-render based on props
  981. instance.setState({ color: "green" });
  982. expect(renderCalls).toBe(1);
  983. // Re-render based on props
  984. instance.setState({ color: "red" });
  985. expect(renderCalls).toBe(2);
  986. // Re-render base on state
  987. instance.refs.apple.cut();
  988. expect(renderCalls).toBe(3);
  989. // No re-render based on state
  990. instance.refs.apple.cut();
  991. expect(renderCalls).toBe(3);
  992. // Re-render based on state again
  993. instance.refs.apple.eatSlice();
  994. expect(renderCalls).toBe(4);
  995. });
  996. it("does not do a deep comparison for a shallow shouldComponentUpdate implementation", () => {
  997. function getInitialState() {
  998. return {
  999. foo: [1, 2, 3],
  1000. bar: { a: 4, b: 5, c: 6 }
  1001. };
  1002. }
  1003. var renderCalls = 0;
  1004. var initialSettings = getInitialState();
  1005. class Component extends React.Component {
  1006. state = initialSettings;
  1007. shouldComponentUpdate(nextProps, nextState) {
  1008. var a = shallowCompare(this, nextProps, nextState);
  1009. console.log(a, "!!!");
  1010. return a;
  1011. }
  1012. render() {
  1013. renderCalls++;
  1014. return <div />;
  1015. }
  1016. }
  1017. var container = document.createElement("div");
  1018. var instance = ReactDOM.render(<Component />, container);
  1019. expect(renderCalls).toBe(1);
  1020. // Do not re-render if state is equal
  1021. var settings = {
  1022. foo: initialSettings.foo,
  1023. bar: initialSettings.bar
  1024. };
  1025. instance.setState(settings);
  1026. expect(renderCalls).toBe(1);
  1027. // Re-render because one field changed
  1028. initialSettings.foo = [1, 2, 3];
  1029. instance.setState(initialSettings);
  1030. expect(renderCalls).toBe(2);
  1031. // Re-render because the object changed
  1032. instance.setState(getInitialState());
  1033. expect(renderCalls).toBe(3);
  1034. });
  1035. it("should call setState callback with no arguments", () => {
  1036. let mockArgs;
  1037. class Component extends React.Component {
  1038. componentDidMount() {
  1039. this.setState({}, (...args) => (mockArgs = args));
  1040. }
  1041. render() {
  1042. return false;
  1043. }
  1044. }
  1045. ReactTestUtils.renderIntoDocument(<Component />);
  1046. expect(mockArgs.length).toEqual(0);
  1047. });
  1048. it("确保子组件即便更新被阻止,新虚拟DOM也要移值旧的虚拟DOM的_hostNode过来", () => {
  1049. class Component extends React.Component {
  1050. constructor(props) {
  1051. super(props);
  1052. this.state = {
  1053. a: 1
  1054. };
  1055. }
  1056. render() {
  1057. return <Child />;
  1058. }
  1059. }
  1060. class Child extends React.Component {
  1061. constructor(props) {
  1062. super(props);
  1063. this.state = {
  1064. a: 1
  1065. };
  1066. }
  1067. shouldComponentUpdate() {
  1068. return false;
  1069. }
  1070. render() {
  1071. return <div>{new Date() - 0}</div>;
  1072. }
  1073. }
  1074. var b = <Component />;
  1075. var container = document.createElement("div")
  1076. var s = ReactDOM.render(b, container);
  1077. expect(!!s.mayInst.hostNode).toBe(true)
  1078. s.setState({a:2})
  1079. expect(!!s.mayInst.hostNode).toBe(true)
  1080. });
  1081. });