Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81162 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
/*jslint evil: true */
13
14
"use strict";
15
16
require('mock-modules')
17
.dontMock('ExecutionEnvironment')
18
.dontMock('React')
19
.dontMock('ReactMount')
20
.dontMock('ReactServerRendering')
21
.dontMock('ReactTestUtils')
22
.dontMock('ReactMarkupChecksum');
23
24
var mocks = require('mocks');
25
26
var ExecutionEnvironment;
27
var React;
28
var ReactMarkupChecksum;
29
var ReactMount;
30
var ReactReconcileTransaction;
31
var ReactTestUtils;
32
var ReactServerRendering;
33
34
var ID_ATTRIBUTE_NAME;
35
36
describe('ReactServerRendering', function() {
37
beforeEach(function() {
38
require('mock-modules').dumpCache();
39
React = require('React');
40
ReactMarkupChecksum = require('ReactMarkupChecksum');
41
ReactMount = require('ReactMount');
42
ReactTestUtils = require('ReactTestUtils');
43
ReactReconcileTransaction = require('ReactReconcileTransaction');
44
45
ExecutionEnvironment = require('ExecutionEnvironment');
46
ExecutionEnvironment.canUseDOM = false;
47
ReactServerRendering = require('ReactServerRendering');
48
49
var DOMProperty = require('DOMProperty');
50
ID_ATTRIBUTE_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
51
});
52
53
describe('renderComponentToString', function() {
54
it('should generate simple markup', function() {
55
var response = ReactServerRendering.renderToString(
56
<span>hello world</span>
57
);
58
expect(response).toMatch(
59
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
60
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">hello world</span>'
61
);
62
});
63
64
it('should not register event listeners', function() {
65
var EventPluginHub = require('EventPluginHub');
66
var cb = mocks.getMockFunction();
67
68
ReactServerRendering.renderToString(
69
<span onClick={cb}>hello world</span>
70
);
71
expect(EventPluginHub.__getListenerBank()).toEqual({});
72
});
73
74
it('should render composite components', function() {
75
var Parent = React.createClass({
76
render: function() {
77
return <div><Child name="child" /></div>;
78
}
79
});
80
var Child = React.createClass({
81
render: function() {
82
return <span>My name is {this.props.name}</span>;
83
}
84
});
85
var response = ReactServerRendering.renderToString(
86
<Parent />
87
);
88
expect(response).toMatch(
89
'<div ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
90
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
91
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">' +
92
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">My name is </span>' +
93
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">child</span>' +
94
'</span>' +
95
'</div>'
96
);
97
});
98
99
it('should only execute certain lifecycle methods', function() {
100
function runTest() {
101
var lifecycle = [];
102
var TestComponent = React.createClass({
103
componentWillMount: function() {
104
lifecycle.push('componentWillMount');
105
},
106
componentDidMount: function() {
107
lifecycle.push('componentDidMount');
108
},
109
getInitialState: function() {
110
lifecycle.push('getInitialState');
111
return {name: 'TestComponent'};
112
},
113
render: function() {
114
lifecycle.push('render');
115
return <span>Component name: {this.state.name}</span>;
116
},
117
componentWillUpdate: function() {
118
lifecycle.push('componentWillUpdate');
119
},
120
componentDidUpdate: function() {
121
lifecycle.push('componentDidUpdate');
122
},
123
shouldComponentUpdate: function() {
124
lifecycle.push('shouldComponentUpdate');
125
},
126
componentWillReceiveProps: function() {
127
lifecycle.push('componentWillReceiveProps');
128
},
129
componentWillUnmount: function() {
130
lifecycle.push('componentWillUnmount');
131
}
132
});
133
134
var response = ReactServerRendering.renderToString(
135
<TestComponent />
136
);
137
138
expect(response).toMatch(
139
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
140
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
141
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">Component name: </span>' +
142
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">TestComponent</span>' +
143
'</span>'
144
);
145
expect(lifecycle).toEqual(
146
['getInitialState', 'componentWillMount', 'render']
147
);
148
}
149
150
runTest();
151
152
// This should work the same regardless of whether you can use DOM or not.
153
ExecutionEnvironment.canUseDOM = true;
154
runTest();
155
});
156
157
it('should have the correct mounting behavior', function() {
158
// This test is testing client-side behavior.
159
ExecutionEnvironment.canUseDOM = true;
160
161
var mountCount = 0;
162
var numClicks = 0;
163
164
var TestComponent = React.createClass({
165
componentDidMount: function() {
166
mountCount++;
167
},
168
click: function() {
169
numClicks++;
170
},
171
render: function() {
172
return (
173
<span ref="span" onClick={this.click}>Name: {this.props.name}</span>
174
);
175
}
176
});
177
178
var element = document.createElement('div');
179
React.render(<TestComponent />, element);
180
181
var lastMarkup = element.innerHTML;
182
183
// Exercise the update path. Markup should not change,
184
// but some lifecycle methods should be run again.
185
React.render(<TestComponent name="x" />, element);
186
expect(mountCount).toEqual(1);
187
188
// Unmount and remount. We should get another mount event and
189
// we should get different markup, as the IDs are unique each time.
190
React.unmountComponentAtNode(element);
191
expect(element.innerHTML).toEqual('');
192
React.render(<TestComponent name="x" />, element);
193
expect(mountCount).toEqual(2);
194
expect(element.innerHTML).not.toEqual(lastMarkup);
195
196
// Now kill the node and render it on top of server-rendered markup, as if
197
// we used server rendering. We should mount again, but the markup should
198
// be unchanged. We will append a sentinel at the end of innerHTML to be
199
// sure that innerHTML was not changed.
200
React.unmountComponentAtNode(element);
201
expect(element.innerHTML).toEqual('');
202
203
ExecutionEnvironment.canUseDOM = false;
204
lastMarkup = ReactServerRendering.renderToString(
205
<TestComponent name="x" />
206
);
207
ExecutionEnvironment.canUseDOM = true;
208
element.innerHTML = lastMarkup + ' __sentinel__';
209
210
React.render(<TestComponent name="x" />, element);
211
expect(mountCount).toEqual(3);
212
expect(element.innerHTML.indexOf('__sentinel__') > -1).toBe(true);
213
React.unmountComponentAtNode(element);
214
expect(element.innerHTML).toEqual('');
215
216
// Now simulate a situation where the app is not idempotent. React should
217
// warn but do the right thing.
218
var _warn = console.warn;
219
console.warn = mocks.getMockFunction();
220
element.innerHTML = lastMarkup;
221
var instance = React.render(<TestComponent name="y" />, element);
222
expect(mountCount).toEqual(4);
223
expect(console.warn.mock.calls.length).toBe(1);
224
expect(element.innerHTML.length > 0).toBe(true);
225
expect(element.innerHTML).not.toEqual(lastMarkup);
226
console.warn = _warn;
227
228
// Ensure the events system works
229
expect(numClicks).toEqual(0);
230
ReactTestUtils.Simulate.click(instance.refs.span.getDOMNode());
231
expect(numClicks).toEqual(1);
232
});
233
234
it('should throw with silly args', function() {
235
expect(
236
ReactServerRendering.renderToString.bind(
237
ReactServerRendering,
238
'not a component'
239
)
240
).toThrow(
241
'Invariant Violation: renderToString(): You must pass ' +
242
'a valid ReactElement.'
243
);
244
});
245
});
246
247
describe('renderComponentToStaticMarkup', function() {
248
it('should not put checksum and React ID on components', function() {
249
var lifecycle = [];
250
var NestedComponent = React.createClass({
251
render: function() {
252
return <div>inner text</div>;
253
}
254
});
255
256
var TestComponent = React.createClass({
257
render: function() {
258
lifecycle.push('render');
259
return <span><NestedComponent /></span>;
260
}
261
});
262
263
var response = ReactServerRendering.renderToStaticMarkup(
264
<TestComponent />
265
);
266
267
expect(response).toBe('<span><div>inner text</div></span>');
268
});
269
270
it('should not put checksum and React ID on text components', function() {
271
var TestComponent = React.createClass({
272
render: function() {
273
return <span>{'hello'} {'world'}</span>;
274
}
275
});
276
277
var response = ReactServerRendering.renderToStaticMarkup(
278
<TestComponent />
279
);
280
281
expect(response).toBe('<span>hello world</span>');
282
});
283
284
it('should not register event listeners', function() {
285
var EventPluginHub = require('EventPluginHub');
286
var cb = mocks.getMockFunction();
287
288
ReactServerRendering.renderToString(
289
<span onClick={cb}>hello world</span>
290
);
291
expect(EventPluginHub.__getListenerBank()).toEqual({});
292
});
293
294
it('should only execute certain lifecycle methods', function() {
295
function runTest() {
296
var lifecycle = [];
297
var TestComponent = React.createClass({
298
componentWillMount: function() {
299
lifecycle.push('componentWillMount');
300
},
301
componentDidMount: function() {
302
lifecycle.push('componentDidMount');
303
},
304
getInitialState: function() {
305
lifecycle.push('getInitialState');
306
return {name: 'TestComponent'};
307
},
308
render: function() {
309
lifecycle.push('render');
310
return <span>Component name: {this.state.name}</span>;
311
},
312
componentWillUpdate: function() {
313
lifecycle.push('componentWillUpdate');
314
},
315
componentDidUpdate: function() {
316
lifecycle.push('componentDidUpdate');
317
},
318
shouldComponentUpdate: function() {
319
lifecycle.push('shouldComponentUpdate');
320
},
321
componentWillReceiveProps: function() {
322
lifecycle.push('componentWillReceiveProps');
323
},
324
componentWillUnmount: function() {
325
lifecycle.push('componentWillUnmount');
326
}
327
});
328
329
var response = ReactServerRendering.renderToStaticMarkup(
330
<TestComponent />
331
);
332
333
expect(response).toBe('<span>Component name: TestComponent</span>');
334
expect(lifecycle).toEqual(
335
['getInitialState', 'componentWillMount', 'render']
336
);
337
}
338
339
runTest();
340
341
// This should work the same regardless of whether you can use DOM or not.
342
ExecutionEnvironment.canUseDOM = true;
343
runTest();
344
});
345
346
it('should throw with silly args', function() {
347
expect(
348
ReactServerRendering.renderToStaticMarkup.bind(
349
ReactServerRendering,
350
'not a component'
351
)
352
).toThrow(
353
'Invariant Violation: renderToStaticMarkup(): You must pass ' +
354
'a valid ReactElement.'
355
);
356
});
357
358
it('allows setState in componentWillMount without using DOM', function() {
359
var Component = React.createClass({
360
componentWillMount: function() {
361
this.setState({text: 'hello, world'});
362
},
363
render: function() {
364
return <div>{this.state.text}</div>;
365
}
366
});
367
368
ReactReconcileTransaction.prototype.perform = function() {
369
// We shouldn't ever be calling this on the server
370
throw new Error('Browser reconcile transaction should not be used');
371
};
372
var markup = ReactServerRendering.renderToString(
373
<Component />
374
);
375
expect(markup.indexOf('hello, world') >= 0).toBe(true);
376
});
377
});
378
});
379
380