@samitouri / QOS-React / commits / 73038840ef

Move ShallowRenderer tests (#10184)

Toru Kobayashi committed Aug 1, 2017 at 16:27 UTC 73038840efbaf96bb45095b3fb571bda6beec8a3
2 files changed +661 -639
src/renderers/dom/test/__tests__/ReactTestUtils-test.js
-639
@@ -12,7 +12,6 @@
12 'use strict';
13
14 let createRenderer;
15 -let PropTypes;
15 let React;
16 let ReactDOM;
17 let ReactDOMServer;
@@ -21,625 +20,12 @@ let ReactTestUtils;
20 describe('ReactTestUtils', () => {
21 beforeEach(() => {
22 createRenderer = require('react-test-renderer/shallow').createRenderer;
24 - PropTypes = require('prop-types');
23 React = require('react');
24 ReactDOM = require('react-dom');
25 ReactDOMServer = require('react-dom/server');
26 ReactTestUtils = require('react-dom/test-utils');
27 });
28
31 - it('should call all of the lifecycle hooks', () => {
32 - const logs = [];
33 - const logger = message => () => logs.push(message) || true;
34 -
35 - class SomeComponent extends React.Component {
36 - componentWillMount = logger('componentWillMount');
37 - componentDidMount = logger('componentDidMount');
38 - componentWillReceiveProps = logger('componentWillReceiveProps');
39 - shouldComponentUpdate = logger('shouldComponentUpdate');
40 - componentWillUpdate = logger('componentWillUpdate');
41 - componentDidUpdate = logger('componentDidUpdate');
42 - componentWillUnmount = logger('componentWillUnmount');
43 - render() {
44 - return <div />;
45 - }
46 - }
47 -
48 - const shallowRenderer = createRenderer();
49 - shallowRenderer.render(<SomeComponent foo={1} />);
50 -
51 - // Calling cDU might lead to problems with host component references.
52 - // Since our components aren't really mounted, refs won't be available.
53 - expect(logs).toEqual(['componentWillMount']);
54 -
55 - logs.splice(0);
56 -
57 - const instance = shallowRenderer.getMountedInstance();
58 - instance.setState({});
59 -
60 - // The previous shallow renderer triggered cDU for setState() calls.
61 - expect(logs).toEqual([
62 - 'shouldComponentUpdate',
63 - 'componentWillUpdate',
64 - 'componentDidUpdate',
65 - ]);
66 -
67 - logs.splice(0);
68 -
69 - shallowRenderer.render(<SomeComponent foo={2} />);
70 -
71 - // The previous shallow renderer did not trigger cDU for props changes.
72 - expect(logs).toEqual([
73 - 'componentWillReceiveProps',
74 - 'shouldComponentUpdate',
75 - 'componentWillUpdate',
76 - ]);
77 - });
78 -
79 - it('should only render 1 level deep', () => {
80 - function Parent() {
81 - return <div><Child /></div>;
82 - }
83 - function Child() {
84 - throw Error('This component should not render');
85 - }
86 -
87 - const shallowRenderer = createRenderer();
88 - shallowRenderer.render(React.createElement(Parent));
89 - });
90 -
91 - it('should have shallow rendering', () => {
92 - class SomeComponent extends React.Component {
93 - render() {
94 - return (
95 - <div>
96 - <span className="child1" />
97 - <span className="child2" />
98 - </div>
99 - );
100 - }
101 - }
102 -
103 - const shallowRenderer = createRenderer();
104 - const result = shallowRenderer.render(<SomeComponent />);
105 -
106 - expect(result.type).toBe('div');
107 - expect(result.props.children).toEqual([
108 - <span className="child1" />,
109 - <span className="child2" />,
110 - ]);
111 - });
112 -
113 - it('should enable shouldComponentUpdate to prevent a re-render', () => {
114 - let renderCounter = 0;
115 - class SimpleComponent extends React.Component {
116 - state = {update: false};
117 - shouldComponentUpdate(nextProps, nextState) {
118 - return this.state.update !== nextState.update;
119 - }
120 - render() {
121 - renderCounter++;
122 - return <div>{`${renderCounter}`}</div>;
123 - }
124 - }
125 -
126 - const shallowRenderer = createRenderer();
127 - shallowRenderer.render(<SimpleComponent />);
128 - expect(shallowRenderer.getRenderOutput()).toEqual(<div>1</div>);
129 -
130 - const instance = shallowRenderer.getMountedInstance();
131 - instance.setState({update: false});
132 - expect(shallowRenderer.getRenderOutput()).toEqual(<div>1</div>);
133 -
134 - instance.setState({update: true});
135 - expect(shallowRenderer.getRenderOutput()).toEqual(<div>2</div>);
136 - });
137 -
138 - it('should shallow render a functional component', () => {
139 - function SomeComponent(props, context) {
140 - return (
141 - <div>
142 - <div>{props.foo}</div>
143 - <div>{context.bar}</div>
144 - <span className="child1" />
145 - <span className="child2" />
146 - </div>
147 - );
148 - }
149 -
150 - const shallowRenderer = createRenderer();
151 - const result = shallowRenderer.render(<SomeComponent foo={'FOO'} />, {
152 - bar: 'BAR',
153 - });
154 -
155 - expect(result.type).toBe('div');
156 - expect(result.props.children).toEqual([
157 - <div>FOO</div>,
158 - <div>BAR</div>,
159 - <span className="child1" />,
160 - <span className="child2" />,
161 - ]);
162 - });
163 -
164 - it('should shallow render a component returning strings directly from render', () => {
165 - const Text = ({value}) => value;
166 -
167 - const shallowRenderer = createRenderer();
168 - const result = shallowRenderer.render(<Text value="foo" />);
169 - expect(result).toEqual('foo');
170 - });
171 -
172 - it('should shallow render a component returning numbers directly from render', () => {
173 - const Text = ({value}) => value;
174 -
175 - const shallowRenderer = createRenderer();
176 - const result = shallowRenderer.render(<Text value={10} />);
177 - expect(result).toEqual(10);
178 - });
179 -
180 - it('should shallow render a fragment', () => {
181 - class SomeComponent extends React.Component {
182 - render() {
183 - return <div />;
184 - }
185 - }
186 - class Fragment extends React.Component {
187 - render() {
188 - return [<div key="a" />, <span key="b" />, <SomeComponent />];
189 - }
190 - }
191 - const shallowRenderer = createRenderer();
192 - const result = shallowRenderer.render(<Fragment />);
193 - expect(result).toEqual([
194 - <div key="a" />,
195 - <span key="b" />,
196 - <SomeComponent />,
197 - ]);
198 - });
199 -
200 - it('should throw for invalid elements', () => {
201 - class SomeComponent extends React.Component {
202 - render() {
203 - return <div />;
204 - }
205 - }
206 -
207 - const shallowRenderer = createRenderer();
208 - expect(() => shallowRenderer.render(SomeComponent)).toThrowError(
209 - 'ReactShallowRenderer render(): Invalid component element. Instead of ' +
210 - 'passing a component class, make sure to instantiate it by passing it ' +
211 - 'to React.createElement.',
212 - );
213 - expect(() => shallowRenderer.render(<div />)).toThrowError(
214 - 'ReactShallowRenderer render(): Shallow rendering works only with ' +
215 - 'custom components, not primitives (div). Instead of calling ' +
216 - '`.render(el)` and inspecting the rendered output, look at `el.props` ' +
217 - 'directly instead.',
218 - );
219 - });
220 -
221 - it('should have shallow unmounting', () => {
222 - const componentWillUnmount = jest.fn();
223 -
224 - class SomeComponent extends React.Component {
225 - componentWillUnmount = componentWillUnmount;
226 - render() {
227 - return <div />;
228 - }
229 - }
230 -
231 - const shallowRenderer = createRenderer();
232 - shallowRenderer.render(<SomeComponent />);
233 - shallowRenderer.unmount();
234 -
235 - expect(componentWillUnmount).toBeCalled();
236 - });
237 -
238 - it('can shallow render to null', () => {
239 - class SomeComponent extends React.Component {
240 - render() {
241 - return null;
242 - }
243 - }
244 -
245 - const shallowRenderer = createRenderer();
246 - const result = shallowRenderer.render(<SomeComponent />);
247 -
248 - expect(result).toBe(null);
249 - });
250 -
251 - it('can shallow render with a ref', () => {
252 - class SomeComponent extends React.Component {
253 - render() {
254 - return <div ref="hello" />;
255 - }
256 - }
257 -
258 - const shallowRenderer = createRenderer();
259 - // Shouldn't crash.
260 - shallowRenderer.render(<SomeComponent />);
261 - });
262 -
263 - it('lets you update shallowly rendered components', () => {
264 - class SomeComponent extends React.Component {
265 - state = {clicked: false};
266 -
267 - onClick = () => {
268 - this.setState({clicked: true});
269 - };
270 -
271 - render() {
272 - const className = this.state.clicked ? 'was-clicked' : '';
273 -
274 - if (this.props.aNew === 'prop') {
275 - return (
276 - <a href="#" onClick={this.onClick} className={className}>
277 - Test link
278 - </a>
279 - );
280 - } else {
281 - return (
282 - <div>
283 - <span className="child1" />
284 - <span className="child2" />
285 - </div>
286 - );
287 - }
288 - }
289 - }
290 -
291 - const shallowRenderer = createRenderer();
292 - const result = shallowRenderer.render(<SomeComponent />);
293 - expect(result.type).toBe('div');
294 - expect(result.props.children).toEqual([
295 - <span className="child1" />,
296 - <span className="child2" />,
297 - ]);
298 -
299 - const updatedResult = shallowRenderer.render(<SomeComponent aNew="prop" />);
300 - expect(updatedResult.type).toBe('a');
301 -
302 - const mockEvent = {};
303 - updatedResult.props.onClick(mockEvent);
304 -
305 - const updatedResultCausedByClick = shallowRenderer.getRenderOutput();
306 - expect(updatedResultCausedByClick.type).toBe('a');
307 - expect(updatedResultCausedByClick.props.className).toBe('was-clicked');
308 - });
309 -
310 - it('can access the mounted component instance', () => {
311 - class SimpleComponent extends React.Component {
312 - someMethod = () => {
313 - return this.props.n;
314 - };
315 -
316 - render() {
317 - return <div>{this.props.n}</div>;
318 - }
319 - }
320 -
321 - const shallowRenderer = createRenderer();
322 - shallowRenderer.render(<SimpleComponent n={5} />);
323 - expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5);
324 - });
325 -
326 - it('can shallowly render components with contextTypes', () => {
327 - class SimpleComponent extends React.Component {
328 - static contextTypes = {
329 - name: PropTypes.string,
330 - };
331 -
332 - render() {
333 - return <div />;
334 - }
335 - }
336 -
337 - const shallowRenderer = createRenderer();
338 - const result = shallowRenderer.render(<SimpleComponent />);
339 - expect(result).toEqual(<div />);
340 - });
341 -
342 - it('can shallowly render components with ref as function', () => {
343 - class SimpleComponent extends React.Component {
344 - state = {clicked: false};
345 -
346 - handleUserClick = () => {
347 - this.setState({clicked: true});
348 - };
349 -
350 - render() {
351 - return (
352 - <div
353 - ref={() => {}}
354 - onClick={this.handleUserClick}
355 - className={this.state.clicked ? 'clicked' : ''}
356 - />
357 - );
358 - }
359 - }
360 -
361 - const shallowRenderer = createRenderer();
362 - shallowRenderer.render(<SimpleComponent />);
363 - let result = shallowRenderer.getRenderOutput();
364 - expect(result.type).toEqual('div');
365 - expect(result.props.className).toEqual('');
366 - result.props.onClick();
367 -
368 - result = shallowRenderer.getRenderOutput();
369 - expect(result.type).toEqual('div');
370 - expect(result.props.className).toEqual('clicked');
371 - });
372 -
373 - it('can setState in componentWillMount when shallow rendering', () => {
374 - class SimpleComponent extends React.Component {
375 - componentWillMount() {
376 - this.setState({groovy: 'doovy'});
377 - }
378 -
379 - render() {
380 - return <div>{this.state.groovy}</div>;
381 - }
382 - }
383 -
384 - const shallowRenderer = createRenderer();
385 - const result = shallowRenderer.render(<SimpleComponent />);
386 - expect(result).toEqual(<div>doovy</div>);
387 - });
388 -
389 - it('can setState in componentWillReceiveProps when shallow rendering', () => {
390 - class SimpleComponent extends React.Component {
391 - state = {count: 0};
392 -
393 - componentWillReceiveProps(nextProps) {
394 - if (nextProps.updateState) {
395 - this.setState({count: 1});
396 - }
397 - }
398 -
399 - render() {
400 - return <div>{this.state.count}</div>;
401 - }
402 - }
403 -
404 - const shallowRenderer = createRenderer();
405 - let result = shallowRenderer.render(
406 - <SimpleComponent updateState={false} />,
407 - );
408 - expect(result.props.children).toEqual(0);
409 -
410 - result = shallowRenderer.render(<SimpleComponent updateState={true} />);
411 - expect(result.props.children).toEqual(1);
412 - });
413 -
414 - it('can setState with an updater function', () => {
415 - let instance;
416 -
417 - class SimpleComponent extends React.Component {
418 - state = {
419 - counter: 0,
420 - };
421 -
422 - render() {
423 - instance = this;
424 - return (
425 - <button ref="button" onClick={this.onClick}>
426 - {this.state.counter}
427 - </button>
428 - );
429 - }
430 - }
431 -
432 - const shallowRenderer = createRenderer();
433 - let result = shallowRenderer.render(<SimpleComponent defaultCount={1} />);
434 - expect(result.props.children).toEqual(0);
435 -
436 - instance.setState((state, props) => {
437 - return {counter: props.defaultCount + 1};
438 - });
439 -
440 - result = shallowRenderer.getRenderOutput();
441 - expect(result.props.children).toEqual(2);
442 - });
443 -
444 - it('can setState with a callback', () => {
445 - let instance;
446 -
447 - class SimpleComponent extends React.Component {
448 - state = {
449 - counter: 0,
450 - };
451 - render() {
452 - instance = this;
453 - return (
454 - <p>
455 - {this.state.counter}
456 - </p>
457 - );
458 - }
459 - }
460 -
461 - const shallowRenderer = createRenderer();
462 - const result = shallowRenderer.render(<SimpleComponent />);
463 - expect(result.props.children).toBe(0);
464 -
465 - const callback = jest.fn(function() {
466 - expect(this).toBe(instance);
467 - });
468 -
469 - instance.setState({counter: 1}, callback);
470 -
471 - const updated = shallowRenderer.getRenderOutput();
472 - expect(updated.props.children).toBe(1);
473 - expect(callback).toHaveBeenCalled();
474 - });
475 -
476 - it('can replaceState with a callback', () => {
477 - let instance;
478 -
479 - class SimpleComponent extends React.Component {
480 - state = {
481 - counter: 0,
482 - };
483 - render() {
484 - instance = this;
485 - return (
486 - <p>
487 - {this.state.counter}
488 - </p>
489 - );
490 - }
491 - }
492 -
493 - const shallowRenderer = createRenderer();
494 - const result = shallowRenderer.render(<SimpleComponent />);
495 - expect(result.props.children).toBe(0);
496 -
497 - const callback = jest.fn(function() {
498 - expect(this).toBe(instance);
499 - });
500 -
501 - // No longer a public API, but we can test that it works internally by
502 - // reaching into the updater.
503 - shallowRenderer._updater.enqueueReplaceState(
504 - instance,
505 - {counter: 1},
506 - callback,
507 - );
508 -
509 - const updated = shallowRenderer.getRenderOutput();
510 - expect(updated.props.children).toBe(1);
511 - expect(callback).toHaveBeenCalled();
512 - });
513 -
514 - it('can forceUpdate with a callback', () => {
515 - let instance;
516 -
517 - class SimpleComponent extends React.Component {
518 - state = {
519 - counter: 0,
520 - };
521 - render() {
522 - instance = this;
523 - return (
524 - <p>
525 - {this.state.counter}
526 - </p>
527 - );
528 - }
529 - }
530 -
531 - const shallowRenderer = createRenderer();
532 - const result = shallowRenderer.render(<SimpleComponent />);
533 - expect(result.props.children).toBe(0);
534 -
535 - const callback = jest.fn(function() {
536 - expect(this).toBe(instance);
537 - });
538 -
539 - instance.forceUpdate(callback);
540 -
541 - const updated = shallowRenderer.getRenderOutput();
542 - expect(updated.props.children).toBe(0);
543 - expect(callback).toHaveBeenCalled();
544 - });
545 -
546 - it('can pass context when shallowly rendering', () => {
547 - class SimpleComponent extends React.Component {
548 - static contextTypes = {
549 - name: PropTypes.string,
550 - };
551 -
552 - render() {
553 - return <div>{this.context.name}</div>;
554 - }
555 - }
556 -
557 - const shallowRenderer = createRenderer();
558 - const result = shallowRenderer.render(<SimpleComponent />, {
559 - name: 'foo',
560 - });
561 - expect(result).toEqual(<div>foo</div>);
562 - });
563 -
564 - it('should track context across updates', () => {
565 - class SimpleComponent extends React.Component {
566 - static contextTypes = {
567 - foo: PropTypes.string,
568 - };
569 -
570 - state = {
571 - bar: 'bar',
572 - };
573 -
574 - render() {
575 - return <div>{`${this.context.foo}:${this.state.bar}`}</div>;
576 - }
577 - }
578 -
579 - const shallowRenderer = createRenderer();
580 - let result = shallowRenderer.render(<SimpleComponent />, {
581 - foo: 'foo',
582 - });
583 - expect(result).toEqual(<div>foo:bar</div>);
584 -
585 - const instance = shallowRenderer.getMountedInstance();
586 - instance.setState({bar: 'baz'});
587 -
588 - result = shallowRenderer.getRenderOutput();
589 - expect(result).toEqual(<div>foo:baz</div>);
590 - });
591 -
592 - it('can fail context when shallowly rendering', () => {
593 - spyOn(console, 'error');
594 -
595 - class SimpleComponent extends React.Component {
596 - static contextTypes = {
597 - name: PropTypes.string.isRequired,
598 - };
599 -
600 - render() {
601 - return <div>{this.context.name}</div>;
602 - }
603 - }
604 -
605 - const shallowRenderer = createRenderer();
606 - shallowRenderer.render(<SimpleComponent />);
607 - expectDev(console.error.calls.count()).toBe(1);
608 - expect(
609 - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
610 - ).toBe(
611 - 'Warning: Failed context type: The context `name` is marked as ' +
612 - 'required in `SimpleComponent`, but its value is `undefined`.\n' +
613 - ' in SimpleComponent (at **)',
614 - );
615 - });
616 -
617 - it('should warn about propTypes (but only once)', () => {
618 - spyOn(console, 'error');
619 -
620 - class SimpleComponent extends React.Component {
621 - render() {
622 - return React.createElement('div', null, this.props.name);
623 - }
624 - }
625 -
626 - SimpleComponent.propTypes = {
627 - name: PropTypes.string.isRequired,
628 - };
629 -
630 - const shallowRenderer = createRenderer();
631 - shallowRenderer.render(React.createElement(SimpleComponent, {name: 123}));
632 -
633 - expect(console.error.calls.count()).toBe(1);
634 - expect(
635 - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
636 - ).toBe(
637 - 'Warning: Failed prop type: Invalid prop `name` of type `number` ' +
638 - 'supplied to `SimpleComponent`, expected `string`.\n' +
639 - ' in SimpleComponent',
640 - );
641 - });
642 -
29 it('can scryRenderedDOMComponentsWithClass with TextComponent', () => {
30 class Wrapper extends React.Component {
31 render() {
@@ -917,31 +303,6 @@ describe('ReactTestUtils', () => {
303 expect(hrs.length).toBe(2);
304 });
305
920 - it('should enable rendering of cloned element', () => {
921 - class SimpleComponent extends React.Component {
922 - constructor(props) {
923 - super(props);
924 -
925 - this.state = {
926 - bar: 'bar',
927 - };
928 - }
929 -
930 - render() {
931 - return <div>{`${this.props.foo}:${this.state.bar}`}</div>;
932 - }
933 - }
934 -
935 - const shallowRenderer = createRenderer();
936 - let result = shallowRenderer.render(<SimpleComponent foo="foo" />);
937 - expect(result).toEqual(<div>foo:bar</div>);
938 -
939 - const instance = shallowRenderer.getMountedInstance();
940 - const cloned = React.cloneElement(instance, {foo: 'baz'});
941 - result = shallowRenderer.render(cloned);
942 - expect(result).toEqual(<div>baz:bar</div>);
943 - });
944 -
306 describe('Simulate', () => {
307 it('should set the type of the event', () => {
308 let event;
src/renderers/testing/__tests__/ReactShallowRenderer-test.js new
+661
@@ -0,0 +1,661 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @emails react-core
10 + */
11 +
12 +'use strict';
13 +
14 +let createRenderer;
15 +let PropTypes;
16 +let React;
17 +
18 +describe('ReactTestUtils', () => {
19 + beforeEach(() => {
20 + createRenderer = require('react-test-renderer/shallow').createRenderer;
21 + PropTypes = require('prop-types');
22 + React = require('react');
23 + });
24 +
25 + it('should call all of the lifecycle hooks', () => {
26 + const logs = [];
27 + const logger = message => () => logs.push(message) || true;
28 +
29 + class SomeComponent extends React.Component {
30 + componentWillMount = logger('componentWillMount');
31 + componentDidMount = logger('componentDidMount');
32 + componentWillReceiveProps = logger('componentWillReceiveProps');
33 + shouldComponentUpdate = logger('shouldComponentUpdate');
34 + componentWillUpdate = logger('componentWillUpdate');
35 + componentDidUpdate = logger('componentDidUpdate');
36 + componentWillUnmount = logger('componentWillUnmount');
37 + render() {
38 + return <div />;
39 + }
40 + }
41 +
42 + const shallowRenderer = createRenderer();
43 + shallowRenderer.render(<SomeComponent foo={1} />);
44 +
45 + // Calling cDU might lead to problems with host component references.
46 + // Since our components aren't really mounted, refs won't be available.
47 + expect(logs).toEqual(['componentWillMount']);
48 +
49 + logs.splice(0);
50 +
51 + const instance = shallowRenderer.getMountedInstance();
52 + instance.setState({});
53 +
54 + // The previous shallow renderer triggered cDU for setState() calls.
55 + expect(logs).toEqual([
56 + 'shouldComponentUpdate',
57 + 'componentWillUpdate',
58 + 'componentDidUpdate',
59 + ]);
60 +
61 + logs.splice(0);
62 +
63 + shallowRenderer.render(<SomeComponent foo={2} />);
64 +
65 + // The previous shallow renderer did not trigger cDU for props changes.
66 + expect(logs).toEqual([
67 + 'componentWillReceiveProps',
68 + 'shouldComponentUpdate',
69 + 'componentWillUpdate',
70 + ]);
71 + });
72 +
73 + it('should only render 1 level deep', () => {
74 + function Parent() {
75 + return <div><Child /></div>;
76 + }
77 + function Child() {
78 + throw Error('This component should not render');
79 + }
80 +
81 + const shallowRenderer = createRenderer();
82 + shallowRenderer.render(React.createElement(Parent));
83 + });
84 +
85 + it('should have shallow rendering', () => {
86 + class SomeComponent extends React.Component {
87 + render() {
88 + return (
89 + <div>
90 + <span className="child1" />
91 + <span className="child2" />
92 + </div>
93 + );
94 + }
95 + }
96 +
97 + const shallowRenderer = createRenderer();
98 + const result = shallowRenderer.render(<SomeComponent />);
99 +
100 + expect(result.type).toBe('div');
101 + expect(result.props.children).toEqual([
102 + <span className="child1" />,
103 + <span className="child2" />,
104 + ]);
105 + });
106 +
107 + it('should enable shouldComponentUpdate to prevent a re-render', () => {
108 + let renderCounter = 0;
109 + class SimpleComponent extends React.Component {
110 + state = {update: false};
111 + shouldComponentUpdate(nextProps, nextState) {
112 + return this.state.update !== nextState.update;
113 + }
114 + render() {
115 + renderCounter++;
116 + return <div>{`${renderCounter}`}</div>;
117 + }
118 + }
119 +
120 + const shallowRenderer = createRenderer();
121 + shallowRenderer.render(<SimpleComponent />);
122 + expect(shallowRenderer.getRenderOutput()).toEqual(<div>1</div>);
123 +
124 + const instance = shallowRenderer.getMountedInstance();
125 + instance.setState({update: false});
126 + expect(shallowRenderer.getRenderOutput()).toEqual(<div>1</div>);
127 +
128 + instance.setState({update: true});
129 + expect(shallowRenderer.getRenderOutput()).toEqual(<div>2</div>);
130 + });
131 +
132 + it('should shallow render a functional component', () => {
133 + function SomeComponent(props, context) {
134 + return (
135 + <div>
136 + <div>{props.foo}</div>
137 + <div>{context.bar}</div>
138 + <span className="child1" />
139 + <span className="child2" />
140 + </div>
141 + );
142 + }
143 +
144 + const shallowRenderer = createRenderer();
145 + const result = shallowRenderer.render(<SomeComponent foo={'FOO'} />, {
146 + bar: 'BAR',
147 + });
148 +
149 + expect(result.type).toBe('div');
150 + expect(result.props.children).toEqual([
151 + <div>FOO</div>,
152 + <div>BAR</div>,
153 + <span className="child1" />,
154 + <span className="child2" />,
155 + ]);
156 + });
157 +
158 + it('should shallow render a component returning strings directly from render', () => {
159 + const Text = ({value}) => value;
160 +
161 + const shallowRenderer = createRenderer();
162 + const result = shallowRenderer.render(<Text value="foo" />);
163 + expect(result).toEqual('foo');
164 + });
165 +
166 + it('should shallow render a component returning numbers directly from render', () => {
167 + const Text = ({value}) => value;
168 +
169 + const shallowRenderer = createRenderer();
170 + const result = shallowRenderer.render(<Text value={10} />);
171 + expect(result).toEqual(10);
172 + });
173 +
174 + it('should shallow render a fragment', () => {
175 + class SomeComponent extends React.Component {
176 + render() {
177 + return <div />;
178 + }
179 + }
180 + class Fragment extends React.Component {
181 + render() {
182 + return [<div key="a" />, <span key="b" />, <SomeComponent />];
183 + }
184 + }
185 + const shallowRenderer = createRenderer();
186 + const result = shallowRenderer.render(<Fragment />);
187 + expect(result).toEqual([
188 + <div key="a" />,
189 + <span key="b" />,
190 + <SomeComponent />,
191 + ]);
192 + });
193 +
194 + it('should throw for invalid elements', () => {
195 + class SomeComponent extends React.Component {
196 + render() {
197 + return <div />;
198 + }
199 + }
200 +
201 + const shallowRenderer = createRenderer();
202 + expect(() => shallowRenderer.render(SomeComponent)).toThrowError(
203 + 'ReactShallowRenderer render(): Invalid component element. Instead of ' +
204 + 'passing a component class, make sure to instantiate it by passing it ' +
205 + 'to React.createElement.',
206 + );
207 + expect(() => shallowRenderer.render(<div />)).toThrowError(
208 + 'ReactShallowRenderer render(): Shallow rendering works only with ' +
209 + 'custom components, not primitives (div). Instead of calling ' +
210 + '`.render(el)` and inspecting the rendered output, look at `el.props` ' +
211 + 'directly instead.',
212 + );
213 + });
214 +
215 + it('should have shallow unmounting', () => {
216 + const componentWillUnmount = jest.fn();
217 +
218 + class SomeComponent extends React.Component {
219 + componentWillUnmount = componentWillUnmount;
220 + render() {
221 + return <div />;
222 + }
223 + }
224 +
225 + const shallowRenderer = createRenderer();
226 + shallowRenderer.render(<SomeComponent />);
227 + shallowRenderer.unmount();
228 +
229 + expect(componentWillUnmount).toBeCalled();
230 + });
231 +
232 + it('can shallow render to null', () => {
233 + class SomeComponent extends React.Component {
234 + render() {
235 + return null;
236 + }
237 + }
238 +
239 + const shallowRenderer = createRenderer();
240 + const result = shallowRenderer.render(<SomeComponent />);
241 +
242 + expect(result).toBe(null);
243 + });
244 +
245 + it('can shallow render with a ref', () => {
246 + class SomeComponent extends React.Component {
247 + render() {
248 + return <div ref="hello" />;
249 + }
250 + }
251 +
252 + const shallowRenderer = createRenderer();
253 + // Shouldn't crash.
254 + shallowRenderer.render(<SomeComponent />);
255 + });
256 +
257 + it('lets you update shallowly rendered components', () => {
258 + class SomeComponent extends React.Component {
259 + state = {clicked: false};
260 +
261 + onClick = () => {
262 + this.setState({clicked: true});
263 + };
264 +
265 + render() {
266 + const className = this.state.clicked ? 'was-clicked' : '';
267 +
268 + if (this.props.aNew === 'prop') {
269 + return (
270 + <a href="#" onClick={this.onClick} className={className}>
271 + Test link
272 + </a>
273 + );
274 + } else {
275 + return (
276 + <div>
277 + <span className="child1" />
278 + <span className="child2" />
279 + </div>
280 + );
281 + }
282 + }
283 + }
284 +
285 + const shallowRenderer = createRenderer();
286 + const result = shallowRenderer.render(<SomeComponent />);
287 + expect(result.type).toBe('div');
288 + expect(result.props.children).toEqual([
289 + <span className="child1" />,
290 + <span className="child2" />,
291 + ]);
292 +
293 + const updatedResult = shallowRenderer.render(<SomeComponent aNew="prop" />);
294 + expect(updatedResult.type).toBe('a');
295 +
296 + const mockEvent = {};
297 + updatedResult.props.onClick(mockEvent);
298 +
299 + const updatedResultCausedByClick = shallowRenderer.getRenderOutput();
300 + expect(updatedResultCausedByClick.type).toBe('a');
301 + expect(updatedResultCausedByClick.props.className).toBe('was-clicked');
302 + });
303 +
304 + it('can access the mounted component instance', () => {
305 + class SimpleComponent extends React.Component {
306 + someMethod = () => {
307 + return this.props.n;
308 + };
309 +
310 + render() {
311 + return <div>{this.props.n}</div>;
312 + }
313 + }
314 +
315 + const shallowRenderer = createRenderer();
316 + shallowRenderer.render(<SimpleComponent n={5} />);
317 + expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5);
318 + });
319 +
320 + it('can shallowly render components with contextTypes', () => {
321 + class SimpleComponent extends React.Component {
322 + static contextTypes = {
323 + name: PropTypes.string,
324 + };
325 +
326 + render() {
327 + return <div />;
328 + }
329 + }
330 +
331 + const shallowRenderer = createRenderer();
332 + const result = shallowRenderer.render(<SimpleComponent />);
333 + expect(result).toEqual(<div />);
334 + });
335 +
336 + it('can shallowly render components with ref as function', () => {
337 + class SimpleComponent extends React.Component {
338 + state = {clicked: false};
339 +
340 + handleUserClick = () => {
341 + this.setState({clicked: true});
342 + };
343 +
344 + render() {
345 + return (
346 + <div
347 + ref={() => {}}
348 + onClick={this.handleUserClick}
349 + className={this.state.clicked ? 'clicked' : ''}
350 + />
351 + );
352 + }
353 + }
354 +
355 + const shallowRenderer = createRenderer();
356 + shallowRenderer.render(<SimpleComponent />);
357 + let result = shallowRenderer.getRenderOutput();
358 + expect(result.type).toEqual('div');
359 + expect(result.props.className).toEqual('');
360 + result.props.onClick();
361 +
362 + result = shallowRenderer.getRenderOutput();
363 + expect(result.type).toEqual('div');
364 + expect(result.props.className).toEqual('clicked');
365 + });
366 +
367 + it('can setState in componentWillMount when shallow rendering', () => {
368 + class SimpleComponent extends React.Component {
369 + componentWillMount() {
370 + this.setState({groovy: 'doovy'});
371 + }
372 +
373 + render() {
374 + return <div>{this.state.groovy}</div>;
375 + }
376 + }
377 +
378 + const shallowRenderer = createRenderer();
379 + const result = shallowRenderer.render(<SimpleComponent />);
380 + expect(result).toEqual(<div>doovy</div>);
381 + });
382 +
383 + it('can setState in componentWillReceiveProps when shallow rendering', () => {
384 + class SimpleComponent extends React.Component {
385 + state = {count: 0};
386 +
387 + componentWillReceiveProps(nextProps) {
388 + if (nextProps.updateState) {
389 + this.setState({count: 1});
390 + }
391 + }
392 +
393 + render() {
394 + return <div>{this.state.count}</div>;
395 + }
396 + }
397 +
398 + const shallowRenderer = createRenderer();
399 + let result = shallowRenderer.render(
400 + <SimpleComponent updateState={false} />,
401 + );
402 + expect(result.props.children).toEqual(0);
403 +
404 + result = shallowRenderer.render(<SimpleComponent updateState={true} />);
405 + expect(result.props.children).toEqual(1);
406 + });
407 +
408 + it('can setState with an updater function', () => {
409 + let instance;
410 +
411 + class SimpleComponent extends React.Component {
412 + state = {
413 + counter: 0,
414 + };
415 +
416 + render() {
417 + instance = this;
418 + return (
419 + <button ref="button" onClick={this.onClick}>
420 + {this.state.counter}
421 + </button>
422 + );
423 + }
424 + }
425 +
426 + const shallowRenderer = createRenderer();
427 + let result = shallowRenderer.render(<SimpleComponent defaultCount={1} />);
428 + expect(result.props.children).toEqual(0);
429 +
430 + instance.setState((state, props) => {
431 + return {counter: props.defaultCount + 1};
432 + });
433 +
434 + result = shallowRenderer.getRenderOutput();
435 + expect(result.props.children).toEqual(2);
436 + });
437 +
438 + it('can setState with a callback', () => {
439 + let instance;
440 +
441 + class SimpleComponent extends React.Component {
442 + state = {
443 + counter: 0,
444 + };
445 + render() {
446 + instance = this;
447 + return (
448 + <p>
449 + {this.state.counter}
450 + </p>
451 + );
452 + }
453 + }
454 +
455 + const shallowRenderer = createRenderer();
456 + const result = shallowRenderer.render(<SimpleComponent />);
457 + expect(result.props.children).toBe(0);
458 +
459 + const callback = jest.fn(function() {
460 + expect(this).toBe(instance);
461 + });
462 +
463 + instance.setState({counter: 1}, callback);
464 +
465 + const updated = shallowRenderer.getRenderOutput();
466 + expect(updated.props.children).toBe(1);
467 + expect(callback).toHaveBeenCalled();
468 + });
469 +
470 + it('can replaceState with a callback', () => {
471 + let instance;
472 +
473 + class SimpleComponent extends React.Component {
474 + state = {
475 + counter: 0,
476 + };
477 + render() {
478 + instance = this;
479 + return (
480 + <p>
481 + {this.state.counter}
482 + </p>
483 + );
484 + }
485 + }
486 +
487 + const shallowRenderer = createRenderer();
488 + const result = shallowRenderer.render(<SimpleComponent />);
489 + expect(result.props.children).toBe(0);
490 +
491 + const callback = jest.fn(function() {
492 + expect(this).toBe(instance);
493 + });
494 +
495 + // No longer a public API, but we can test that it works internally by
496 + // reaching into the updater.
497 + shallowRenderer._updater.enqueueReplaceState(
498 + instance,
499 + {counter: 1},
500 + callback,
501 + );
502 +
503 + const updated = shallowRenderer.getRenderOutput();
504 + expect(updated.props.children).toBe(1);
505 + expect(callback).toHaveBeenCalled();
506 + });
507 +
508 + it('can forceUpdate with a callback', () => {
509 + let instance;
510 +
511 + class SimpleComponent extends React.Component {
512 + state = {
513 + counter: 0,
514 + };
515 + render() {
516 + instance = this;
517 + return (
518 + <p>
519 + {this.state.counter}
520 + </p>
521 + );
522 + }
523 + }
524 +
525 + const shallowRenderer = createRenderer();
526 + const result = shallowRenderer.render(<SimpleComponent />);
527 + expect(result.props.children).toBe(0);
528 +
529 + const callback = jest.fn(function() {
530 + expect(this).toBe(instance);
531 + });
532 +
533 + instance.forceUpdate(callback);
534 +
535 + const updated = shallowRenderer.getRenderOutput();
536 + expect(updated.props.children).toBe(0);
537 + expect(callback).toHaveBeenCalled();
538 + });
539 +
540 + it('can pass context when shallowly rendering', () => {
541 + class SimpleComponent extends React.Component {
542 + static contextTypes = {
543 + name: PropTypes.string,
544 + };
545 +
546 + render() {
547 + return <div>{this.context.name}</div>;
548 + }
549 + }
550 +
551 + const shallowRenderer = createRenderer();
552 + const result = shallowRenderer.render(<SimpleComponent />, {
553 + name: 'foo',
554 + });
555 + expect(result).toEqual(<div>foo</div>);
556 + });
557 +
558 + it('should track context across updates', () => {
559 + class SimpleComponent extends React.Component {
560 + static contextTypes = {
561 + foo: PropTypes.string,
562 + };
563 +
564 + state = {
565 + bar: 'bar',
566 + };
567 +
568 + render() {
569 + return <div>{`${this.context.foo}:${this.state.bar}`}</div>;
570 + }
571 + }
572 +
573 + const shallowRenderer = createRenderer();
574 + let result = shallowRenderer.render(<SimpleComponent />, {
575 + foo: 'foo',
576 + });
577 + expect(result).toEqual(<div>foo:bar</div>);
578 +
579 + const instance = shallowRenderer.getMountedInstance();
580 + instance.setState({bar: 'baz'});
581 +
582 + result = shallowRenderer.getRenderOutput();
583 + expect(result).toEqual(<div>foo:baz</div>);
584 + });
585 +
586 + it('can fail context when shallowly rendering', () => {
587 + spyOn(console, 'error');
588 +
589 + class SimpleComponent extends React.Component {
590 + static contextTypes = {
591 + name: PropTypes.string.isRequired,
592 + };
593 +
594 + render() {
595 + return <div>{this.context.name}</div>;
596 + }
597 + }
598 +
599 + const shallowRenderer = createRenderer();
600 + shallowRenderer.render(<SimpleComponent />);
601 + expectDev(console.error.calls.count()).toBe(1);
602 + expect(
603 + console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
604 + ).toBe(
605 + 'Warning: Failed context type: The context `name` is marked as ' +
606 + 'required in `SimpleComponent`, but its value is `undefined`.\n' +
607 + ' in SimpleComponent (at **)',
608 + );
609 + });
610 +
611 + it('should warn about propTypes (but only once)', () => {
612 + spyOn(console, 'error');
613 +
614 + class SimpleComponent extends React.Component {
615 + render() {
616 + return React.createElement('div', null, this.props.name);
617 + }
618 + }
619 +
620 + SimpleComponent.propTypes = {
621 + name: PropTypes.string.isRequired,
622 + };
623 +
624 + const shallowRenderer = createRenderer();
625 + shallowRenderer.render(React.createElement(SimpleComponent, {name: 123}));
626 +
627 + expect(console.error.calls.count()).toBe(1);
628 + expect(
629 + console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
630 + ).toBe(
631 + 'Warning: Failed prop type: Invalid prop `name` of type `number` ' +
632 + 'supplied to `SimpleComponent`, expected `string`.\n' +
633 + ' in SimpleComponent',
634 + );
635 + });
636 +
637 + it('should enable rendering of cloned element', () => {
638 + class SimpleComponent extends React.Component {
639 + constructor(props) {
640 + super(props);
641 +
642 + this.state = {
643 + bar: 'bar',
644 + };
645 + }
646 +
647 + render() {
648 + return <div>{`${this.props.foo}:${this.state.bar}`}</div>;
649 + }
650 + }
651 +
652 + const shallowRenderer = createRenderer();
653 + let result = shallowRenderer.render(<SimpleComponent foo="foo" />);
654 + expect(result).toEqual(<div>foo:bar</div>);
655 +
656 + const instance = shallowRenderer.getMountedInstance();
657 + const cloned = React.cloneElement(instance, {foo: 'baz'});
658 + result = shallowRenderer.render(cloned);
659 + expect(result).toEqual(<div>baz:bar</div>);
660 + });
661 +});