Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81155 views
1
/**
2
* Copyright 2013-2014, 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
var React;
15
var ReactTestUtils;
16
var ReactComponent;
17
var ReactCompositeComponent;
18
var ComponentLifeCycle;
19
var CompositeComponentLifeCycle;
20
21
var clone = function(o) {
22
return JSON.parse(JSON.stringify(o));
23
};
24
25
26
var GET_INIT_STATE_RETURN_VAL = {
27
hasWillMountCompleted: false,
28
hasRenderCompleted: false,
29
hasDidMountCompleted: false,
30
hasWillUnmountCompleted: false
31
};
32
33
var INIT_RENDER_STATE = {
34
hasWillMountCompleted: true,
35
hasRenderCompleted: false,
36
hasDidMountCompleted: false,
37
hasWillUnmountCompleted: false
38
};
39
40
var DID_MOUNT_STATE = {
41
hasWillMountCompleted: true,
42
hasRenderCompleted: true,
43
hasDidMountCompleted: false,
44
hasWillUnmountCompleted: false
45
};
46
47
var NEXT_RENDER_STATE = {
48
hasWillMountCompleted: true,
49
hasRenderCompleted: true,
50
hasDidMountCompleted: true,
51
hasWillUnmountCompleted: false
52
};
53
54
var WILL_UNMOUNT_STATE = {
55
hasWillMountCompleted: true,
56
hasDidMountCompleted: true,
57
hasRenderCompleted: true,
58
hasWillUnmountCompleted: false
59
};
60
61
var POST_WILL_UNMOUNT_STATE = {
62
hasWillMountCompleted: true,
63
hasDidMountCompleted: true,
64
hasRenderCompleted: true,
65
hasWillUnmountCompleted: true
66
};
67
68
/**
69
* TODO: We should make any setState calls fail in
70
* `getInitialState` and `componentWillMount`. They will usually fail
71
* anyways because `this._renderedComponent` is empty, however, if a component
72
* is *reused*, then that won't be the case and things will appear to work in
73
* some cases. Better to just block all updates in initialization.
74
*/
75
describe('ReactComponentLifeCycle', function() {
76
beforeEach(function() {
77
require('mock-modules').dumpCache();
78
React = require('React');
79
ReactTestUtils = require('ReactTestUtils');
80
ReactComponent = require('ReactComponent');
81
ReactCompositeComponent = require('ReactCompositeComponent');
82
ComponentLifeCycle = ReactComponent.LifeCycle;
83
CompositeComponentLifeCycle = ReactCompositeComponent.LifeCycle;
84
});
85
86
it('should not reuse an instance when it has been unmounted', function() {
87
var container = document.createElement('div');
88
var StatefulComponent = React.createClass({
89
getInitialState: function() {
90
return { };
91
},
92
render: function() {
93
return (
94
<div></div>
95
);
96
}
97
});
98
var element = <StatefulComponent />;
99
var firstInstance = React.render(element, container);
100
React.unmountComponentAtNode(container);
101
var secondInstance = React.render(element, container);
102
expect(firstInstance).not.toBe(secondInstance);
103
});
104
105
/**
106
* If a state update triggers rerendering that in turn fires an onDOMReady,
107
* that second onDOMReady should not fail.
108
*/
109
it('it should fire onDOMReady when already in onDOMReady', function() {
110
111
var _testJournal = [];
112
113
var Child = React.createClass({
114
componentDidMount: function() {
115
_testJournal.push('Child:onDOMReady');
116
},
117
render: function() {
118
return <div></div>;
119
}
120
});
121
122
var SwitcherParent = React.createClass({
123
getInitialState: function() {
124
_testJournal.push('SwitcherParent:getInitialState');
125
return {showHasOnDOMReadyComponent: false};
126
},
127
componentDidMount: function() {
128
_testJournal.push('SwitcherParent:onDOMReady');
129
this.switchIt();
130
},
131
switchIt: function() {
132
this.setState({showHasOnDOMReadyComponent: true});
133
},
134
render: function() {
135
return (
136
<div>{
137
this.state.showHasOnDOMReadyComponent ?
138
<Child /> :
139
<div> </div>
140
}</div>
141
);
142
}
143
});
144
145
var instance = <SwitcherParent />;
146
instance = ReactTestUtils.renderIntoDocument(instance);
147
expect(_testJournal).toEqual([
148
'SwitcherParent:getInitialState',
149
'SwitcherParent:onDOMReady',
150
'Child:onDOMReady'
151
]);
152
});
153
154
// You could assign state here, but not access members of it, unless you
155
// had provided a getInitialState method.
156
it('throws when accessing state in componentWillMount', function() {
157
var StatefulComponent = React.createClass({
158
componentWillMount: function() {
159
this.state.yada;
160
},
161
render: function() {
162
return (
163
<div></div>
164
);
165
}
166
});
167
var instance = <StatefulComponent />;
168
expect(function() {
169
instance = ReactTestUtils.renderIntoDocument(instance);
170
}).toThrow();
171
});
172
173
it('should allow update state inside of componentWillMount', function() {
174
var StatefulComponent = React.createClass({
175
componentWillMount: function() {
176
this.setState({stateField: 'something'});
177
},
178
render: function() {
179
return (
180
<div></div>
181
);
182
}
183
});
184
var instance = <StatefulComponent />;
185
expect(function() {
186
instance = ReactTestUtils.renderIntoDocument(instance);
187
}).not.toThrow();
188
});
189
190
it('should allow update state inside of getInitialState', function() {
191
var StatefulComponent = React.createClass({
192
getInitialState: function() {
193
this.setState({stateField: 'something'});
194
195
return {stateField: 'somethingelse'};
196
},
197
render: function() {
198
return (
199
<div></div>
200
);
201
}
202
});
203
var instance = <StatefulComponent />;
204
expect(function() {
205
instance = ReactTestUtils.renderIntoDocument(instance);
206
}).not.toThrow();
207
208
// The return value of getInitialState overrides anything from setState
209
expect(instance.state.stateField).toEqual('somethingelse');
210
});
211
212
213
it('should carry through each of the phases of setup', function() {
214
var LifeCycleComponent = React.createClass({
215
getInitialState: function() {
216
this._testJournal = {};
217
var initState = {
218
hasWillMountCompleted: false,
219
hasDidMountCompleted: false,
220
hasRenderCompleted: false,
221
hasWillUnmountCompleted: false
222
};
223
this._testJournal.returnedFromGetInitialState = clone(initState);
224
this._testJournal.lifeCycleAtStartOfGetInitialState =
225
this._lifeCycleState;
226
this._testJournal.compositeLifeCycleAtStartOfGetInitialState =
227
this._compositeLifeCycleState;
228
return initState;
229
},
230
231
componentWillMount: function() {
232
this._testJournal.stateAtStartOfWillMount = clone(this.state);
233
this._testJournal.lifeCycleAtStartOfWillMount =
234
this._lifeCycleState;
235
this._testJournal.compositeLifeCycleAtStartOfWillMount =
236
this._compositeLifeCycleState;
237
this.state.hasWillMountCompleted = true;
238
},
239
240
componentDidMount: function() {
241
this._testJournal.stateAtStartOfDidMount = clone(this.state);
242
this._testJournal.lifeCycleAtStartOfDidMount =
243
this._lifeCycleState;
244
this.setState({hasDidMountCompleted: true});
245
},
246
247
render: function() {
248
var isInitialRender = !this.state.hasRenderCompleted;
249
if (isInitialRender) {
250
this._testJournal.stateInInitialRender = clone(this.state);
251
this._testJournal.lifeCycleInInitialRender = this._lifeCycleState;
252
this._testJournal.compositeLifeCycleInInitialRender =
253
this._compositeLifeCycleState;
254
} else {
255
this._testJournal.stateInLaterRender = clone(this.state);
256
this._testJournal.lifeCycleInLaterRender = this._lifeCycleState;
257
}
258
// you would *NEVER* do anything like this in real code!
259
this.state.hasRenderCompleted = true;
260
return (
261
<div ref="theDiv">
262
I am the inner DIV
263
</div>
264
);
265
},
266
267
componentWillUnmount: function() {
268
this._testJournal.stateAtStartOfWillUnmount = clone(this.state);
269
this._testJournal.lifeCycleAtStartOfWillUnmount =
270
this._lifeCycleState;
271
this.state.hasWillUnmountCompleted = true;
272
}
273
});
274
275
// A component that is merely "constructed" (as in "constructor") but not
276
// yet initialized, or rendered.
277
//
278
var instance = ReactTestUtils.renderIntoDocument(<LifeCycleComponent />);
279
280
// getInitialState
281
expect(instance._testJournal.returnedFromGetInitialState).toEqual(
282
GET_INIT_STATE_RETURN_VAL
283
);
284
expect(instance._testJournal.lifeCycleAtStartOfGetInitialState)
285
.toBe(ComponentLifeCycle.MOUNTED);
286
expect(instance._testJournal.compositeLifeCycleAtStartOfGetInitialState)
287
.toBe(CompositeComponentLifeCycle.MOUNTING);
288
289
// componentWillMount
290
expect(instance._testJournal.stateAtStartOfWillMount).toEqual(
291
instance._testJournal.returnedFromGetInitialState
292
);
293
expect(instance._testJournal.lifeCycleAtStartOfWillMount)
294
.toBe(ComponentLifeCycle.MOUNTED);
295
expect(instance._testJournal.compositeLifeCycleAtStartOfWillMount)
296
.toBe(CompositeComponentLifeCycle.MOUNTING);
297
298
// componentDidMount
299
expect(instance._testJournal.stateAtStartOfDidMount)
300
.toEqual(DID_MOUNT_STATE);
301
expect(instance._testJournal.lifeCycleAtStartOfDidMount).toBe(
302
ComponentLifeCycle.MOUNTED
303
);
304
305
// render
306
expect(instance._testJournal.stateInInitialRender)
307
.toEqual(INIT_RENDER_STATE);
308
expect(instance._testJournal.lifeCycleInInitialRender).toBe(
309
ComponentLifeCycle.MOUNTED
310
);
311
expect(instance._testJournal.compositeLifeCycleInInitialRender).toBe(
312
CompositeComponentLifeCycle.MOUNTING
313
);
314
315
expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);
316
317
// Now *update the component*
318
instance.forceUpdate();
319
320
// render 2nd time
321
expect(instance._testJournal.stateInLaterRender)
322
.toEqual(NEXT_RENDER_STATE);
323
expect(instance._testJournal.lifeCycleInLaterRender).toBe(
324
ComponentLifeCycle.MOUNTED
325
);
326
327
expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);
328
329
// Now *unmountComponent*
330
instance.unmountComponent();
331
332
expect(instance._testJournal.stateAtStartOfWillUnmount)
333
.toEqual(WILL_UNMOUNT_STATE);
334
// componentWillUnmount called right before unmount.
335
expect(instance._testJournal.lifeCycleAtStartOfWillUnmount).toBe(
336
ComponentLifeCycle.MOUNTED
337
);
338
339
// But the current lifecycle of the component is unmounted.
340
expect(instance._lifeCycleState).toBe(ComponentLifeCycle.UNMOUNTED);
341
expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);
342
});
343
344
it('should throw when calling setProps() on an owned component', function() {
345
/**
346
* calls setProps in an componentDidMount.
347
*/
348
var PropsUpdaterInOnDOMReady = React.createClass({
349
componentDidMount: function() {
350
this.refs.theSimpleComponent.setProps({
351
valueToUseInitially: this.props.valueToUseInOnDOMReady
352
});
353
},
354
render: function() {
355
return (
356
<div
357
className={this.props.valueToUseInitially}
358
ref="theSimpleComponent"
359
/>
360
);
361
}
362
});
363
var instance =
364
<PropsUpdaterInOnDOMReady
365
valueToUseInitially="hello"
366
valueToUseInOnDOMReady="goodbye"
367
/>;
368
expect(function() {
369
instance = ReactTestUtils.renderIntoDocument(instance);
370
}).toThrow(
371
'Invariant Violation: replaceProps(...): You called `setProps` or ' +
372
'`replaceProps` on a component with a parent. This is an anti-pattern ' +
373
'since props will get reactively updated when rendered. Instead, ' +
374
'change the owner\'s `render` method to pass the correct value as ' +
375
'props to the component where it is created.'
376
);
377
});
378
379
it('should not throw when updating an auxiliary component', function() {
380
var Tooltip = React.createClass({
381
render: function() {
382
return <div>{this.props.children}</div>;
383
},
384
componentDidMount: function() {
385
this.container = document.createElement('div');
386
this.updateTooltip();
387
},
388
componentDidUpdate: function() {
389
this.updateTooltip();
390
},
391
updateTooltip: function() {
392
// Even though this.props.tooltip has an owner, updating it shouldn't
393
// throw here because it's mounted as a root component
394
React.render(this.props.tooltip, this.container);
395
}
396
});
397
var Component = React.createClass({
398
render: function() {
399
return (
400
<Tooltip
401
ref="tooltip"
402
tooltip={<div>{this.props.tooltipText}</div>}>
403
{this.props.text}
404
</Tooltip>
405
);
406
}
407
});
408
409
var container = document.createElement('div');
410
var instance = React.render(
411
<Component text="uno" tooltipText="one" />,
412
container
413
);
414
415
// Since `instance` is a root component, we can set its props. This also
416
// makes Tooltip rerender the tooltip component, which shouldn't throw.
417
instance.setProps({text: "dos", tooltipText: "two"});
418
});
419
420
it('should not allow setProps() called on an unmounted element',
421
function() {
422
var PropsToUpdate = React.createClass({
423
render: function() {
424
return <div className={this.props.value} ref="theSimpleComponent" />;
425
}
426
});
427
var instance = <PropsToUpdate value="hello" />;
428
expect(instance.setProps).not.toBeDefined();
429
});
430
431
it('should allow state updates in componentDidMount', function() {
432
/**
433
* calls setState in an componentDidMount.
434
*/
435
var SetStateInComponentDidMount = React.createClass({
436
getInitialState: function() {
437
return {
438
stateField: this.props.valueToUseInitially
439
};
440
},
441
componentDidMount: function() {
442
this.setState({stateField: this.props.valueToUseInOnDOMReady});
443
},
444
render: function() {
445
return (<div></div>);
446
}
447
});
448
var instance =
449
<SetStateInComponentDidMount
450
valueToUseInitially="hello"
451
valueToUseInOnDOMReady="goodbye"
452
/>;
453
instance = ReactTestUtils.renderIntoDocument(instance);
454
expect(instance.state.stateField).toBe('goodbye');
455
});
456
457
it('should call nested lifecycle methods in the right order', function() {
458
var log;
459
var logger = function(msg) {
460
return function() {
461
// return true for shouldComponentUpdate
462
log.push(msg);
463
return true;
464
};
465
};
466
var Outer = React.createClass({
467
render: function() {
468
return <div><Inner x={this.props.x} /></div>;
469
},
470
componentWillMount: logger('outer componentWillMount'),
471
componentDidMount: logger('outer componentDidMount'),
472
componentWillReceiveProps: logger('outer componentWillReceiveProps'),
473
shouldComponentUpdate: logger('outer shouldComponentUpdate'),
474
componentWillUpdate: logger('outer componentWillUpdate'),
475
componentDidUpdate: logger('outer componentDidUpdate'),
476
componentWillUnmount: logger('outer componentWillUnmount')
477
});
478
var Inner = React.createClass({
479
render: function() {
480
return <span>{this.props.x}</span>;
481
},
482
componentWillMount: logger('inner componentWillMount'),
483
componentDidMount: logger('inner componentDidMount'),
484
componentWillReceiveProps: logger('inner componentWillReceiveProps'),
485
shouldComponentUpdate: logger('inner shouldComponentUpdate'),
486
componentWillUpdate: logger('inner componentWillUpdate'),
487
componentDidUpdate: logger('inner componentDidUpdate'),
488
componentWillUnmount: logger('inner componentWillUnmount')
489
});
490
var instance;
491
492
log = [];
493
instance = ReactTestUtils.renderIntoDocument(<Outer x={17} />);
494
expect(log).toEqual([
495
'outer componentWillMount',
496
'inner componentWillMount',
497
'inner componentDidMount',
498
'outer componentDidMount'
499
]);
500
501
log = [];
502
instance.setProps({x: 42});
503
expect(log).toEqual([
504
'outer componentWillReceiveProps',
505
'outer shouldComponentUpdate',
506
'outer componentWillUpdate',
507
'inner componentWillReceiveProps',
508
'inner shouldComponentUpdate',
509
'inner componentWillUpdate',
510
'inner componentDidUpdate',
511
'outer componentDidUpdate'
512
]);
513
514
log = [];
515
instance.unmountComponent();
516
expect(log).toEqual([
517
'outer componentWillUnmount',
518
'inner componentWillUnmount'
519
]);
520
});
521
});
522
523
524