ReactCompositeComponentState-test.jsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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. // https://github.com/facebook/react/blob/master/src/renderers/__tests__/EventPluginHub-test.js
  14. describe("ReactCompositeComponentDOMMinimalism", function() {
  15. // this.timeout(200000);
  16. var TestComponent = class extends React.Component {
  17. constructor(props) {
  18. super(props);
  19. this.peekAtState("getInitialState", undefined, props);
  20. this.state = { color: "red" };
  21. }
  22. peekAtState = (from, state = this.state, props = this.props) => {
  23. props.stateListener(from, state && state.color);
  24. };
  25. peekAtCallback = from => {
  26. return () => this.peekAtState(from);
  27. };
  28. setFavoriteColor(nextColor) {
  29. this.setState({ color: nextColor }, this.peekAtCallback("setFavoriteColor"));
  30. }
  31. render() {
  32. this.peekAtState("render");
  33. return <div>{this.state.color}</div>;
  34. }
  35. componentWillMount() {
  36. this.peekAtState("componentWillMount-start");
  37. this.setState(function(state) {
  38. this.peekAtState("before-setState-sunrise", state);
  39. });
  40. this.setState({ color: "sunrise" }, this.peekAtCallback("setState-sunrise"));
  41. this.setState(function(state) {
  42. this.peekAtState("after-setState-sunrise", state);
  43. });
  44. this.peekAtState("componentWillMount-after-sunrise");
  45. this.setState({ color: "orange" }, this.peekAtCallback("setState-orange"));
  46. this.setState(function(state) {
  47. this.peekAtState("after-setState-orange", state);
  48. });
  49. this.peekAtState("componentWillMount-end");
  50. }
  51. componentDidMount() {
  52. this.peekAtState("componentDidMount-start");
  53. this.setState({ color: "yellow" }, this.peekAtCallback("setState-yellow"));
  54. this.peekAtState("componentDidMount-end");
  55. }
  56. componentWillReceiveProps(newProps) {
  57. this.peekAtState("componentWillReceiveProps-start");
  58. if (newProps.nextColor) {
  59. this.setState(function(state) {
  60. this.peekAtState("before-setState-receiveProps", state);
  61. return { color: newProps.nextColor };
  62. });
  63. // No longer a public API, but we can test that it works internally by
  64. // reaching into the updater.
  65. this.setState({ color: undefined });
  66. this.setState(function(state) {
  67. this.peekAtState("before-setState-again-receiveProps", state);
  68. return { color: newProps.nextColor };
  69. }, this.peekAtCallback("setState-receiveProps"));
  70. this.setState(function(state) {
  71. this.peekAtState("after-setState-receiveProps", state);
  72. });
  73. }
  74. this.peekAtState("componentWillReceiveProps-end");
  75. }
  76. shouldComponentUpdate(nextProps, nextState) {
  77. this.peekAtState("shouldComponentUpdate-currentState");
  78. this.peekAtState("shouldComponentUpdate-nextState", nextState);
  79. return true;
  80. }
  81. componentWillUpdate(nextProps, nextState) {
  82. this.peekAtState("componentWillUpdate-currentState");
  83. this.peekAtState("componentWillUpdate-nextState", nextState);
  84. }
  85. componentDidUpdate(prevProps, prevState) {
  86. this.peekAtState("componentDidUpdate-currentState");
  87. this.peekAtState("componentDidUpdate-prevState", prevState);
  88. }
  89. componentWillUnmount() {
  90. this.peekAtState("componentWillUnmount");
  91. }
  92. };
  93. /*it("should support setting state", () => {
  94. var container = document.createElement("div");
  95. document.body.appendChild(container);
  96. var stateListener = spyOn.createSpy();
  97. var instance = ReactDOM.render(<TestComponent stateListener={stateListener} />, container, function peekAtInitialCallback() {
  98. this.peekAtState("initial-callback");
  99. });
  100. ReactDOM.render(<TestComponent stateListener={stateListener} nextColor="green" />, container, instance.peekAtCallback("setProps"));
  101. instance.setFavoriteColor("blue");
  102. instance.forceUpdate(instance.peekAtCallback("forceUpdate"));
  103. ReactDOM.unmountComponentAtNode(container);
  104. let expected = [
  105. // there is no state when getInitialState() is called
  106. ["getInitialState", null],
  107. ["componentWillMount-start", "red"],
  108. // setState()'s only enqueue pending states.
  109. ["componentWillMount-after-sunrise", "red"],
  110. ["componentWillMount-end", "red"],
  111. // pending state queue is processed
  112. ["before-setState-sunrise", "red"],
  113. ["after-setState-sunrise", "sunrise"],
  114. ["after-setState-orange", "orange"],
  115. // pending state has been applied
  116. ["render", "orange"],
  117. ["componentDidMount-start", "orange"],
  118. // setState-sunrise and setState-orange should be called here,
  119. // after the bug in #1740
  120. // componentDidMount() called setState({color:'yellow'}), which is async.
  121. // The update doesn't happen until the next flush.
  122. ["componentDidMount-end", "orange"],
  123. ["shouldComponentUpdate-currentState", "orange"],
  124. ["shouldComponentUpdate-nextState", "yellow"],
  125. ["componentWillUpdate-currentState", "orange"],
  126. ["componentWillUpdate-nextState", "yellow"],
  127. ["render", "yellow"],
  128. ["componentDidUpdate-currentState", "yellow"],
  129. ["componentDidUpdate-prevState", "orange"],
  130. ["setState-sunrise", "yellow"],
  131. ["setState-orange", "yellow"],
  132. ["setState-yellow", "yellow"],
  133. ["initial-callback", "yellow"],
  134. ["componentWillReceiveProps-start", "yellow"],
  135. // setState({color:'green'}) only enqueues a pending state.
  136. ["componentWillReceiveProps-end", "yellow"],
  137. // pending state queue is processed
  138. // We keep updates in the queue to support
  139. // replaceState(prevState => newState).
  140. ["before-setState-receiveProps", "yellow"],
  141. ["before-setState-again-receiveProps", void 666],
  142. ["after-setState-receiveProps", "green"],
  143. ["shouldComponentUpdate-currentState", "yellow"],
  144. ["shouldComponentUpdate-nextState", "green"],
  145. ["componentWillUpdate-currentState", "yellow"],
  146. ["componentWillUpdate-nextState", "green"],
  147. // setFavoriteColor('blue')
  148. ["render", "green"],
  149. ["componentDidUpdate-currentState", "green"],
  150. ["componentDidUpdate-prevState", "yellow"],
  151. ["setState-receiveProps", "green"],
  152. ["setProps", "green"],
  153. // setFavoriteColor('blue')
  154. ["shouldComponentUpdate-currentState", "green"],
  155. ["shouldComponentUpdate-nextState", "blue"],
  156. ["componentWillUpdate-currentState", "green"],
  157. ["componentWillUpdate-nextState", "blue"],
  158. ["render", "blue"],
  159. ["componentDidUpdate-currentState", "blue"],
  160. ["componentDidUpdate-prevState", "green"],
  161. ["setFavoriteColor", "blue"],
  162. // forceUpdate()
  163. ["componentWillUpdate-currentState", "blue"],
  164. ["componentWillUpdate-nextState", "blue"],
  165. ["render", "blue"],
  166. ["componentDidUpdate-currentState", "blue"],
  167. ["componentDidUpdate-prevState", "blue"],
  168. ["forceUpdate", "blue"],
  169. // unmountComponent()
  170. // state is available within `componentWillUnmount()`
  171. ["componentWillUnmount", "blue"]
  172. ];
  173. expect(stateListener.calls.join("\n")).toEqual(expected.join("\n"));
  174. });*/
  175. it("should call componentDidUpdate of children first", () => {});
  176. it("should batch unmounts", () => {
  177. var outer;
  178. class Inner extends React.Component {
  179. render() {
  180. return <div />;
  181. }
  182. componentWillUnmount() {
  183. // This should get silently ignored (maybe with a warning), but it
  184. // shouldn't break React.
  185. outer.setState({ showInner: false });
  186. }
  187. }
  188. class Outer extends React.Component {
  189. state = { showInner: true };
  190. render() {
  191. return <div>{this.state.showInner && <Inner />}</div>;
  192. }
  193. }
  194. var container = document.createElement("div");
  195. outer = ReactDOM.render(<Outer />, container);
  196. expect(() => {
  197. ReactDOM.unmountComponentAtNode(container);
  198. }).not.toThrow();
  199. });
  200. it("should update state when called from child cWRP", function() {
  201. const log = [];
  202. class Parent extends React.Component {
  203. state = { value: "one" };
  204. render() {
  205. log.push("parent render " + this.state.value);
  206. return <Child parent={this} value={this.state.value} />;
  207. }
  208. }
  209. let updated = false;
  210. class Child extends React.Component {
  211. componentWillReceiveProps() {
  212. if (updated) {
  213. return;
  214. }
  215. log.push("child componentWillReceiveProps " + this.props.value);
  216. this.props.parent.setState({ value: "two" });
  217. log.push("child componentWillReceiveProps done " + this.props.value);
  218. updated = true;
  219. }
  220. render() {
  221. log.push("child render " + this.props.value);
  222. return <div>{this.props.value}</div>;
  223. }
  224. }
  225. var container = document.createElement("div");
  226. ReactDOM.render(<Parent />, container);
  227. ReactDOM.render(<Parent />, container);
  228. expect(log).toEqual([
  229. "parent render one",
  230. "child render one",
  231. "parent render one",
  232. "child componentWillReceiveProps one",
  233. "child componentWillReceiveProps done one",
  234. "child render one",
  235. "parent render two",
  236. "child render two"
  237. ]);
  238. });
  239. it("should merge state when sCU returns false", function() {
  240. const log = [];
  241. class Test extends React.Component {
  242. state = { a: 0 };
  243. render() {
  244. return null;
  245. }
  246. shouldComponentUpdate(nextProps, nextState) {
  247. log.push("scu from " + Object.keys(this.state) + " to " + Object.keys(nextState));
  248. return false;
  249. }
  250. }
  251. const container = document.createElement("div");
  252. const test = ReactDOM.render(<Test />, container);
  253. test.setState({ b: 0 });
  254. expect(log.length).toBe(1);
  255. test.setState({ c: 0 });
  256. expect(log.length).toBe(2);
  257. expect(log).toEqual(["scu from a to a,b", "scu from a,b to a,b,c"]);
  258. });
  259. it("should treat assigning to this.state inside cWRP as a replaceState, with a warning", () => {
  260. spyOn(console, "error");
  261. let ops = [];
  262. class Test extends React.Component {
  263. state = { step: 1, extra: true };
  264. componentWillReceiveProps() {
  265. this.setState({ step: 2 }, () => {
  266. // Tests that earlier setState callbacks are not dropped
  267. ops.push(`callback -- step: ${this.state.step}, extra: ${!!this.state.extra}`);
  268. });
  269. // Treat like replaceState
  270. this.state = { step: 3 };
  271. }
  272. render() {
  273. ops.push(`render -- step: ${this.state.step}, extra: ${!!this.state.extra}`);
  274. return null;
  275. }
  276. }
  277. // Mount
  278. const container = document.createElement("div");
  279. ReactDOM.render(<Test />, container);
  280. // Update
  281. ReactDOM.render(<Test />, container);
  282. expect(ops).toEqual(["render -- step: 1, extra: true", "render -- step: 2, extra: false", "callback -- step: 2, extra: false"]);
  283. /*
  284. expect(console.error.calls.count()).toEqual(1);
  285. expect(console.error.calls.argsFor(0)[0]).toEqual(
  286. 'Warning: Test.componentWillReceiveProps(): Assigning directly to ' +
  287. "this.state is deprecated (except inside a component's constructor). " +
  288. 'Use setState instead.',
  289. );/**/
  290. });
  291. });