Skip to content

Commit 1ab33f4

Browse files
committed
Add tests for all hydrate warning cases like in fixtures/ssr (#10085)
1 parent 0d149b1 commit 1ab33f4

File tree

1 file changed

+306
-10
lines changed

1 file changed

+306
-10
lines changed

packages/react-dom/src/__tests__/ReactMount-test.js

Lines changed: 306 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ describe('ReactMount', () => {
157157
});
158158

159159
it('should warn when a hydrated element has inner text mismatch', () => {
160+
// See fixtures/ssr: ssr-warnForTextDifference
161+
160162
class Component extends React.Component {
161163
render() {
162164
return this.props.children;
@@ -187,7 +189,69 @@ describe('ReactMount', () => {
187189
);
188190
});
189191

192+
it('should warn when hydrating a text node over a mismatching text node', () => {
193+
// See fixtures/ssr: ssr-warnForTextDifference-warnForUnmatchedText-didNotMatchHydratedContainerTextInstance
194+
195+
const div = document.createElement('div');
196+
const markup = ReactDOMServer.renderToString('server text');
197+
div.innerHTML = markup;
198+
199+
expect(() => ReactDOM.hydrate('client text', div)).toWarnDev(
200+
'Text content did not match. ' +
201+
'Server: "server text" ' +
202+
'Client: "client text"',
203+
);
204+
});
205+
206+
it('should warn when a hydrated element has first text match but second text mismatch', () => {
207+
// See fixtures/ssr: ssr-warnForTextDifference-warnForUnmatchedText-didNotMatchHydratedTextInstance
208+
209+
class Component extends React.Component {
210+
render() {
211+
return this.props.children;
212+
}
213+
}
214+
215+
const serverRandom = Math.random();
216+
const clientRandom = Math.random();
217+
218+
const div = document.createElement('div');
219+
const markup = ReactDOMServer.renderToString(
220+
<Component>
221+
<em>
222+
{'SSRMismatchTest static text and '}
223+
{'server random text ' + serverRandom}
224+
</em>
225+
</Component>,
226+
);
227+
div.innerHTML = markup;
228+
229+
expect(() =>
230+
ReactDOM.hydrate(
231+
<Component>
232+
<em>
233+
{'SSRMismatchTest static text and '}
234+
{'client random text ' + clientRandom}
235+
</em>
236+
</Component>,
237+
div,
238+
),
239+
).toWarnDev(
240+
'Text content did not match. ' +
241+
'Server: "server random text ' +
242+
serverRandom +
243+
'" ' +
244+
'Client: "client random text ' +
245+
clientRandom +
246+
'"\n' +
247+
' in em (at **)\n' +
248+
' in Component (at **)',
249+
);
250+
});
251+
190252
it('should warn when a hydrated element has children mismatch', () => {
253+
// See fixtures/ssr: ssr-warnForInsertedHydratedText-didNotFindHydratableTextInstance
254+
191255
class Component extends React.Component {
192256
render() {
193257
return this.props.children;
@@ -242,43 +306,275 @@ describe('ReactMount', () => {
242306
);
243307
});
244308

245-
it('should warn when a hydrated element has extra props', () => {
309+
it('should warn when a hydrated element has extra props with non-null values', () => {
310+
// See fixtures/ssr: ssr-warnForPropDifference
311+
246312
const div = document.createElement('div');
247-
const markup = ReactDOMServer.renderToString(<div />);
313+
const markup = ReactDOMServer.renderToString(
314+
<div>
315+
<em>SSRMismatchTest default text</em>
316+
</div>,
317+
);
248318
div.innerHTML = markup;
249319

250320
expect(() =>
251321
ReactDOM.hydrate(
252-
<div data-ssr-prop-mismatch={true} data-ssr-prop-mismatch-2={true} />,
322+
<div data-ssr-extra-prop={true} data-ssr-extra-prop-2={true}>
323+
<em>SSRMismatchTest default text</em>
324+
</div>,
253325
div,
254326
),
255327
).toWarnDev(
256-
'Prop `data-ssr-prop-mismatch` did not match. ' +
328+
'Prop `data-ssr-extra-prop` did not match. ' +
257329
'Server: "null" ' +
258330
'Client: "true"\n' +
259331
' in div (at **)',
260332
);
261333
});
262334

263-
it('should not warn when a hydrated element has an extra prop explicitly set to null', () => {
335+
it('should not warn when a hydrated element has extra props explicitly set to null', () => {
336+
// See fixtures/ssr: ssr-warnForPropDifference-null-no-warning
337+
264338
const div = document.createElement('div');
265-
const markup = ReactDOMServer.renderToString(<div />);
339+
const markup = ReactDOMServer.renderToString(
340+
<div>
341+
<em>SSRMismatchTest default text</em>
342+
</div>,
343+
);
266344
div.innerHTML = markup;
267345

268346
expect(() =>
269-
ReactDOM.hydrate(<div data-ssr-prop-mismatch={null} />, div),
347+
ReactDOM.hydrate(
348+
<div data-ssr-extra-prop={null} data-ssr-extra-prop-2={null}>
349+
<em>SSRMismatchTest default text</em>
350+
</div>,
351+
div,
352+
),
270353
).toWarnDev([]);
271354
});
272355

273356
it('should warn when a server element has extra props', () => {
357+
// See fixtures/ssr: ssr-warnForExtraAttributes
358+
359+
const div = document.createElement('div');
360+
const markup = ReactDOMServer.renderToString(
361+
<div data-ssr-extra-prop={true} data-ssr-extra-prop-2={true}>
362+
<em>SSRMismatchTest default text</em>
363+
</div>,
364+
);
365+
div.innerHTML = markup;
366+
367+
expect(() =>
368+
ReactDOM.hydrate(
369+
<div>
370+
<em>SSRMismatchTest default text</em>
371+
</div>,
372+
div,
373+
),
374+
).toWarnDev(
375+
'Extra attributes from the server: data-ssr-extra-prop,data-ssr-extra-prop-2\n' +
376+
' in div (at **)',
377+
);
378+
});
379+
380+
it('should warn when a browser element has an event handler which is set to false', () => {
381+
// See fixtures/ssr: ssr-warnForInvalidEventListener-false
382+
383+
const div = document.createElement('div');
384+
const markup = ReactDOMServer.renderToString(<div onClick={() => {}} />);
385+
div.innerHTML = markup;
386+
387+
expect(() => ReactDOM.hydrate(<div onClick={false} />, div)).toWarnDev(
388+
'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
389+
'If you used to conditionally omit it with onClick={condition && value}, ' +
390+
'pass onClick={condition ? value : undefined} instead.\n' +
391+
' in div (at **)',
392+
);
393+
});
394+
395+
it('should warn when a browser element has an event handler which is set to a non-function, non-false value', () => {
396+
// See fixtures/ssr: ssr-warnForInvalidEventListener-typeof
397+
398+
const div = document.createElement('div');
399+
const markup = ReactDOMServer.renderToString(<div onClick={() => {}} />);
400+
div.innerHTML = markup;
401+
402+
expect(() => ReactDOM.hydrate(<div onClick={'a string'} />, div)).toWarnDev(
403+
'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
404+
' in div (at **)',
405+
);
406+
});
407+
408+
it('should warn when hydrate removes an element from a server-rendered sequence in the root container', () => {
409+
// See fixtures/ssr: ssr-warnForDeletedHydratableElement-didNotHydrateContainerInstance
410+
411+
const div = document.createElement('div');
412+
const markup =
413+
'SSRMismatchTest first text' +
414+
'<br />' +
415+
'<br />' +
416+
'SSRMismatchTest second text';
417+
div.innerHTML = markup;
418+
419+
expect(() =>
420+
ReactDOM.hydrate(
421+
[
422+
'SSRMismatchTest first text',
423+
<br key={1} />,
424+
'SSRMismatchTest second text',
425+
],
426+
div,
427+
),
428+
).toWarnDev('Did not expect server HTML to contain a <br> in <div>.');
429+
});
430+
431+
it('should warn when hydrate removes an element from a server-rendered sequence', () => {
432+
// See fixtures/ssr: ssr-warnForDeletedHydratableElement-didNotHydrateInstance
433+
434+
const div = document.createElement('div');
435+
const markup = ReactDOMServer.renderToString(
436+
<div>
437+
<div />
438+
<span />
439+
</div>,
440+
);
441+
div.innerHTML = markup;
442+
443+
expect(() =>
444+
ReactDOM.hydrate(
445+
<div>
446+
<span />
447+
</div>,
448+
div,
449+
),
450+
).toWarnDev(
451+
'Did not expect server HTML to contain a <div> in <div>.\n' +
452+
' in span (at **)\n' +
453+
' in div (at **)',
454+
);
455+
});
456+
457+
it('should warn when hydrate removes a text node from a server-rendered sequence in the root container', () => {
458+
// See fixtures/ssr: ssr-warnForDeletedHydratableText-didNotHydrateContainerInstance
459+
460+
const div = document.createElement('div');
461+
const markup =
462+
'SSRMismatchTest server text' + '<br />' + 'SSRMismatchTest default text';
463+
div.innerHTML = markup;
464+
465+
expect(() =>
466+
ReactDOM.hydrate([<br key={1} />, 'SSRMismatchTest default text'], div),
467+
).toWarnDev(
468+
'Did not expect server HTML to contain the text node "SSRMismatchTest server text" in <div>.\n' +
469+
' in br (at **)',
470+
);
471+
});
472+
473+
it('should warn when hydrate removes a text node from a server-rendered sequence', () => {
474+
// See fixtures/ssr: ssr-warnForDeletedHydratableText-didNotHydrateInstance
475+
476+
const div = document.createElement('div');
477+
const markup = ReactDOMServer.renderToString(
478+
<div>
479+
SSRMismatchTest server text
480+
<span />
481+
</div>,
482+
);
483+
div.innerHTML = markup;
484+
485+
expect(() =>
486+
ReactDOM.hydrate(
487+
<div>
488+
<span />
489+
</div>,
490+
div,
491+
),
492+
).toWarnDev(
493+
'Did not expect server HTML to contain the text node "SSRMismatchTest server text" in <div>.\n' +
494+
' in span (at **)\n' +
495+
' in div (at **)',
496+
);
497+
});
498+
499+
it('should warn when hydrate inserts an element to replace a text node in the root container', () => {
500+
// See fixtures/ssr: ssr-warnForInsertedHydratedElement-didNotFindHydratableContainerInstance
501+
502+
const div = document.createElement('div');
503+
const markup = 'SSRMismatchTest default text';
504+
div.innerHTML = markup;
505+
506+
expect(() =>
507+
ReactDOM.hydrate(<span>SSRMismatchTest default text</span>, div),
508+
).toWarnDev(
509+
'Expected server HTML to contain a matching <span> in <div>.\n' +
510+
' in span (at **)',
511+
);
512+
});
513+
514+
it('should warn when hydrate inserts an element to replace a different element', () => {
515+
// See fixtures/ssr: ssr-warnForInsertedHydratedElement-didNotFindHydratableInstance
516+
517+
const div = document.createElement('div');
518+
const markup = ReactDOMServer.renderToString(
519+
<div>
520+
<em>SSRMismatchTest default text</em>
521+
</div>,
522+
);
523+
div.innerHTML = markup;
524+
525+
expect(() =>
526+
ReactDOM.hydrate(
527+
<div>
528+
<p>SSRMismatchTest default text</p>
529+
</div>,
530+
div,
531+
),
532+
).toWarnDev(
533+
'Expected server HTML to contain a matching <p> in <div>.\n' +
534+
' in p (at **)\n' +
535+
' in div (at **)',
536+
);
537+
});
538+
539+
it('should warn when hydrate inserts a text node to replace an element in the root container', () => {
540+
// See fixtures/ssr: ssr-warnForInsertedHydratedText-didNotFindHydratableContainerTextInstance
541+
274542
const div = document.createElement('div');
275543
const markup = ReactDOMServer.renderToString(
276-
<div data-ssr-prop-extra={true} data-ssr-prop-extra-2={true} />,
544+
<span>SSRMismatchTest default text</span>,
277545
);
278546
div.innerHTML = markup;
279547

280-
expect(() => ReactDOM.hydrate(<div />, div)).toWarnDev(
281-
'Extra attributes from the server: data-ssr-prop-extra,data-ssr-prop-extra-2\n' +
548+
expect(() =>
549+
ReactDOM.hydrate('SSRMismatchTest default text', div),
550+
).toWarnDev(
551+
'Expected server HTML to contain a matching text node for "SSRMismatchTest default text" in <div>.',
552+
);
553+
});
554+
555+
it('should warn when hydrate inserts a text node between matching elements', () => {
556+
// See fixtures/ssr: ssr-warnForInsertedHydratedText-didNotFindHydratableTextInstance
557+
558+
const div = document.createElement('div');
559+
const markup = ReactDOMServer.renderToString(
560+
<div>
561+
<span />
562+
<span />
563+
</div>,
564+
);
565+
div.innerHTML = markup;
566+
567+
expect(() =>
568+
ReactDOM.hydrate(
569+
<div>
570+
<span />
571+
SSRMismatchTest client text
572+
<span />
573+
</div>,
574+
div,
575+
),
576+
).toWarnDev(
577+
'Expected server HTML to contain a matching text node for "SSRMismatchTest client text" in <div>.\n' +
282578
' in div (at **)',
283579
);
284580
});

0 commit comments

Comments
 (0)