65
);
66
}
67
68
+async function renderIntoString(reactElement, errorCount = 0) {
69
+ return await expectErrors(
70
+ () =>
71
+ new Promise(resolve =>
72
+ resolve(ReactDOMServer.renderToString(reactElement))),
73
+ errorCount,
74
+ );
75
+}
76
+
77
// Renders text using SSR and then stuffs it into a DOM node; returns the DOM
78
// element that corresponds with the reactElement.
79
// Does not render on client or perform client-side revival.
80
async function serverRender(reactElement, errorCount = 0) {
72
- const markup = await expectErrors(
73
- () => Promise.resolve(ReactDOMServer.renderToString(reactElement)),
74
- errorCount,
75
- );
81
+ const markup = await renderIntoString(reactElement, errorCount);
82
var domElement = document.createElement('div');
83
domElement.innerHTML = markup;
84
return domElement.firstChild;
90
};
91
92
const clientRenderOnServerString = async (element, errorCount = 0) => {
87
- const markup = await serverRender(element, errorCount);
93
+ const markup = await renderIntoString(element, errorCount);
94
+ resetModules();
95
var domElement = document.createElement('div');
96
domElement.innerHTML = markup;
90
- return renderIntoDom(element, domElement, errorCount);
97
+ const serverElement = domElement.firstChild;
98
+ const clientElement = await renderIntoDom(element, domElement, errorCount);
99
+ // assert that the DOM element hasn't been replaced.
100
+ // Note that we cannot use expect(serverElement).toBe(clientElement) because
101
+ // of jest bug #1772
102
+ expect(serverElement === clientElement).toBe(true);
103
+ return clientElement;
104
};
105
106
const clientRenderOnBadMarkup = (element, errorCount = 0) => {
148
testFn(clientRenderOnBadMarkup));
149
}
150
151
+// When there is a test that renders on server and then on client and expects a logged
152
+// error, you want to see the error show up both on server and client. Unfortunately,
153
+// React refuses to issue the same error twice to avoid clogging up the console.
154
+// To get around this, we must reload React modules in between server and client render.
155
+function resetModules() {
156
+ jest.resetModuleRegistry();
157
+ React = require('React');
158
+ ReactDOM = require('ReactDOM');
159
+ ReactDOMServer = require('ReactDOMServer');
160
+ ExecutionEnvironment = require('ExecutionEnvironment');
161
+}
162
+
163
describe('ReactDOMServerIntegration', () => {
164
beforeEach(() => {
140
- jest.resetModuleRegistry();
141
- React = require('React');
142
- ReactDOM = require('ReactDOM');
143
- ReactDOMServer = require('ReactDOMServer');
165
+ resetModules();
166
145
- ExecutionEnvironment = require('ExecutionEnvironment');
167
ExecutionEnvironment.canUseDOM = false;
168
});
169
190
expect(e.firstChild.tagName).toBe('BR');
191
});
192
});
193
+
194
+ describe('property to attribute mapping', function() {
195
+ describe('string properties', function() {
196
+ itRenders('simple numbers', async render => {
197
+ const e = await render(<div width={30} />);
198
+ expect(e.getAttribute('width')).toBe('30');
199
+ });
200
+
201
+ itRenders('simple strings', async render => {
202
+ const e = await render(<div width={'30'} />);
203
+ expect(e.getAttribute('width')).toBe('30');
204
+ });
205
+
206
+ // this seems like it might mask programmer error, but it's existing behavior.
207
+ itRenders('string prop with true value', async render => {
208
+ const e = await render(<a href={true} />);
209
+ expect(e.getAttribute('href')).toBe('true');
210
+ });
211
+
212
+ // this seems like it might mask programmer error, but it's existing behavior.
213
+ itRenders('string prop with false value', async render => {
214
+ const e = await render(<a href={false} />);
215
+ expect(e.getAttribute('href')).toBe('false');
216
+ });
217
+ });
218
+
219
+ describe('boolean properties', function() {
220
+ itRenders('boolean prop with true value', async render => {
221
+ const e = await render(<div hidden={true} />);
222
+ expect(e.getAttribute('hidden')).toBe('');
223
+ });
224
+
225
+ itRenders('boolean prop with false value', async render => {
226
+ const e = await render(<div hidden={false} />);
227
+ expect(e.getAttribute('hidden')).toBe(null);
228
+ });
229
+
230
+ itRenders('boolean prop with self value', async render => {
231
+ const e = await render(<div hidden="hidden" />);
232
+ expect(e.getAttribute('hidden')).toBe('');
233
+ });
234
+
235
+ // this does not seem like correct behavior, since hidden="" in HTML indicates
236
+ // that the boolean property is present. however, it is how the current code
237
+ // behaves, so the test is included here.
238
+ itRenders('boolean prop with "" value', async render => {
239
+ const e = await render(<div hidden="" />);
240
+ expect(e.getAttribute('hidden')).toBe(null);
241
+ });
242
+
243
+ // this seems like it might mask programmer error, but it's existing behavior.
244
+ itRenders('boolean prop with string value', async render => {
245
+ const e = await render(<div hidden="foo" />);
246
+ expect(e.getAttribute('hidden')).toBe('');
247
+ });
248
+
249
+ // this seems like it might mask programmer error, but it's existing behavior.
250
+ itRenders('boolean prop with array value', async render => {
251
+ const e = await render(<div hidden={['foo', 'bar']} />);
252
+ expect(e.getAttribute('hidden')).toBe('');
253
+ });
254
+
255
+ // this seems like it might mask programmer error, but it's existing behavior.
256
+ itRenders('boolean prop with object value', async render => {
257
+ const e = await render(<div hidden={{foo: 'bar'}} />);
258
+ expect(e.getAttribute('hidden')).toBe('');
259
+ });
260
+
261
+ // this seems like it might mask programmer error, but it's existing behavior.
262
+ itRenders('boolean prop with non-zero number value', async render => {
263
+ const e = await render(<div hidden={10} />);
264
+ expect(e.getAttribute('hidden')).toBe('');
265
+ });
266
+
267
+ // this seems like it might mask programmer error, but it's existing behavior.
268
+ itRenders('boolean prop with zero value', async render => {
269
+ const e = await render(<div hidden={0} />);
270
+ expect(e.getAttribute('hidden')).toBe(null);
271
+ });
272
+ });
273
+
274
+ describe('download property (combined boolean/string attribute)', function() {
275
+ itRenders('download prop with true value', async render => {
276
+ const e = await render(<a download={true} />);
277
+ expect(e.getAttribute('download')).toBe('');
278
+ });
279
+
280
+ itRenders('download prop with false value', async render => {
281
+ const e = await render(<a download={false} />);
282
+ expect(e.getAttribute('download')).toBe(null);
283
+ });
284
+
285
+ itRenders('download prop with string value', async render => {
286
+ const e = await render(<a download="myfile" />);
287
+ expect(e.getAttribute('download')).toBe('myfile');
288
+ });
289
+
290
+ itRenders('download prop with string "true" value', async render => {
291
+ const e = await render(<a download={'true'} />);
292
+ expect(e.getAttribute('download')).toBe('true');
293
+ });
294
+ });
295
+
296
+ describe('className property', function() {
297
+ itRenders('className prop with string value', async render => {
298
+ const e = await render(<div className="myClassName" />);
299
+ expect(e.getAttribute('class')).toBe('myClassName');
300
+ });
301
+
302
+ itRenders('className prop with empty string value', async render => {
303
+ const e = await render(<div className="" />);
304
+ expect(e.getAttribute('class')).toBe('');
305
+ });
306
+
307
+ // this probably is just masking programmer error, but it is existing behavior.
308
+ itRenders('className prop with true value', async render => {
309
+ const e = await render(<div className={true} />);
310
+ expect(e.getAttribute('class')).toBe('true');
311
+ });
312
+
313
+ // this probably is just masking programmer error, but it is existing behavior.
314
+ itRenders('className prop with false value', async render => {
315
+ const e = await render(<div className={false} />);
316
+ expect(e.getAttribute('class')).toBe('false');
317
+ });
318
+ });
319
+
320
+ describe('htmlFor property', function() {
321
+ itRenders('htmlFor with string value', async render => {
322
+ const e = await render(<div htmlFor="myFor" />);
323
+ expect(e.getAttribute('for')).toBe('myFor');
324
+ });
325
+
326
+ itRenders('htmlFor with an empty string', async render => {
327
+ const e = await render(<div htmlFor="" />);
328
+ expect(e.getAttribute('for')).toBe('');
329
+ });
330
+
331
+ // this probably is just masking programmer error, but it is existing behavior.
332
+ itRenders('className prop with true value', async render => {
333
+ const e = await render(<div htmlFor={true} />);
334
+ expect(e.getAttribute('for')).toBe('true');
335
+ });
336
+
337
+ // this probably is just masking programmer error, but it is existing behavior.
338
+ itRenders('className prop with false value', async render => {
339
+ const e = await render(<div htmlFor={false} />);
340
+ expect(e.getAttribute('for')).toBe('false');
341
+ });
342
+ });
343
+
344
+ describe('props with special meaning in React', function() {
345
+ itRenders('no ref attribute', async render => {
346
+ class RefComponent extends React.Component {
347
+ render() {
348
+ return <div ref="foo" />;
349
+ }
350
+ }
351
+ const e = await render(<RefComponent />);
352
+ expect(e.getAttribute('ref')).toBe(null);
353
+ });
354
+
355
+ itRenders('no children attribute', async render => {
356
+ const e = await render(React.createElement('div', {}, 'foo'));
357
+ expect(e.getAttribute('children')).toBe(null);
358
+ });
359
+
360
+ itRenders('no key attribute', async render => {
361
+ const e = await render(<div key="foo" />);
362
+ expect(e.getAttribute('key')).toBe(null);
363
+ });
364
+
365
+ itRenders('no dangerouslySetInnerHTML attribute', async render => {
366
+ const e = await render(
367
+ <div dangerouslySetInnerHTML={{__html: 'foo'}} />,
368
+ );
369
+ expect(e.getAttribute('dangerouslySetInnerHTML')).toBe(null);
370
+ });
371
+ });
372
+
373
+ describe('unknown attributes', function() {
374
+ itRenders('no unknown attributes', async render => {
375
+ const e = await render(<div foo="bar" />, 1);
376
+ expect(e.getAttribute('foo')).toBe(null);
377
+ });
378
+
379
+ itRenders('unknown data- attributes', async render => {
380
+ const e = await render(<div data-foo="bar" />);
381
+ expect(e.getAttribute('data-foo')).toBe('bar');
382
+ });
383
+
384
+ itRenders(
385
+ 'no unknown attributes for non-standard elements',
386
+ async render => {
387
+ const e = await render(<nonstandard foo="bar" />, 1);
388
+ expect(e.getAttribute('foo')).toBe(null);
389
+ },
390
+ );
391
+
392
+ itRenders('unknown attributes for custom elements', async render => {
393
+ const e = await render(<custom-element foo="bar" />);
394
+ expect(e.getAttribute('foo')).toBe('bar');
395
+ });
396
+
397
+ itRenders(
398
+ 'unknown attributes for custom elements using is',
399
+ async render => {
400
+ const e = await render(<div is="custom-element" foo="bar" />);
401
+ expect(e.getAttribute('foo')).toBe('bar');
402
+ },
403
+ );
404
+ });
405
+
406
+ itRenders('no HTML events', async render => {
407
+ const e = await render(<div onClick={() => {}} />);
408
+ expect(e.getAttribute('onClick')).toBe(null);
409
+ expect(e.getAttribute('onClick')).toBe(null);
410
+ expect(e.getAttribute('click')).toBe(null);
411
+ });
412
+ });
413
});