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
+});