mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Fix hydration of non-string dangerousSetInnerHTML.__html (#13353)
* Consistently handle non-string dangerousSetInnerHTML.__html in SSR * Add another test
This commit is contained in:
+85
-4
@@ -485,16 +485,97 @@ describe('ReactDOMServerIntegration', () => {
|
||||
expect(e.tagName).toBe('BUTTON');
|
||||
});
|
||||
|
||||
itRenders('a div with dangerouslySetInnerHTML', async render => {
|
||||
const e = await render(
|
||||
<div dangerouslySetInnerHTML={{__html: "<span id='child'/>"}} />,
|
||||
);
|
||||
itRenders('a div with dangerouslySetInnerHTML number', async render => {
|
||||
// Put dangerouslySetInnerHTML one level deeper because otherwise
|
||||
// hydrating from a bad markup would cause a mismatch (since we don't
|
||||
// patch dangerouslySetInnerHTML as text content).
|
||||
const e = (await render(
|
||||
<div>
|
||||
<span dangerouslySetInnerHTML={{__html: 0}} />
|
||||
</div>,
|
||||
)).firstChild;
|
||||
expect(e.childNodes.length).toBe(1);
|
||||
expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
|
||||
expect(e.textContent).toBe('0');
|
||||
});
|
||||
|
||||
itRenders('a div with dangerouslySetInnerHTML boolean', async render => {
|
||||
// Put dangerouslySetInnerHTML one level deeper because otherwise
|
||||
// hydrating from a bad markup would cause a mismatch (since we don't
|
||||
// patch dangerouslySetInnerHTML as text content).
|
||||
const e = (await render(
|
||||
<div>
|
||||
<span dangerouslySetInnerHTML={{__html: false}} />
|
||||
</div>,
|
||||
)).firstChild;
|
||||
expect(e.childNodes.length).toBe(1);
|
||||
expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
|
||||
expect(e.firstChild.data).toBe('false');
|
||||
});
|
||||
|
||||
itRenders(
|
||||
'a div with dangerouslySetInnerHTML text string',
|
||||
async render => {
|
||||
// Put dangerouslySetInnerHTML one level deeper because otherwise
|
||||
// hydrating from a bad markup would cause a mismatch (since we don't
|
||||
// patch dangerouslySetInnerHTML as text content).
|
||||
const e = (await render(
|
||||
<div>
|
||||
<span dangerouslySetInnerHTML={{__html: 'hello'}} />
|
||||
</div>,
|
||||
)).firstChild;
|
||||
expect(e.childNodes.length).toBe(1);
|
||||
expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
|
||||
expect(e.textContent).toBe('hello');
|
||||
},
|
||||
);
|
||||
|
||||
itRenders(
|
||||
'a div with dangerouslySetInnerHTML element string',
|
||||
async render => {
|
||||
const e = await render(
|
||||
<div dangerouslySetInnerHTML={{__html: "<span id='child'/>"}} />,
|
||||
);
|
||||
expect(e.childNodes.length).toBe(1);
|
||||
expect(e.firstChild.tagName).toBe('SPAN');
|
||||
expect(e.firstChild.getAttribute('id')).toBe('child');
|
||||
expect(e.firstChild.childNodes.length).toBe(0);
|
||||
},
|
||||
);
|
||||
|
||||
itRenders('a div with dangerouslySetInnerHTML object', async render => {
|
||||
const obj = {
|
||||
toString() {
|
||||
return "<span id='child'/>";
|
||||
},
|
||||
};
|
||||
const e = await render(<div dangerouslySetInnerHTML={{__html: obj}} />);
|
||||
expect(e.childNodes.length).toBe(1);
|
||||
expect(e.firstChild.tagName).toBe('SPAN');
|
||||
expect(e.firstChild.getAttribute('id')).toBe('child');
|
||||
expect(e.firstChild.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
itRenders(
|
||||
'a div with dangerouslySetInnerHTML set to null',
|
||||
async render => {
|
||||
const e = await render(
|
||||
<div dangerouslySetInnerHTML={{__html: null}} />,
|
||||
);
|
||||
expect(e.childNodes.length).toBe(0);
|
||||
},
|
||||
);
|
||||
|
||||
itRenders(
|
||||
'a div with dangerouslySetInnerHTML set to undefined',
|
||||
async render => {
|
||||
const e = await render(
|
||||
<div dangerouslySetInnerHTML={{__html: undefined}} />,
|
||||
);
|
||||
expect(e.childNodes.length).toBe(0);
|
||||
},
|
||||
);
|
||||
|
||||
describe('newline-eating elements', function() {
|
||||
itRenders(
|
||||
'a newline-eating tag with content not starting with \\n',
|
||||
|
||||
+34
@@ -412,6 +412,40 @@ describe('ReactDOMServerIntegration', () => {
|
||||
<div dangerouslySetInnerHTML={{__html: "<span id='child2'/>"}} />,
|
||||
));
|
||||
|
||||
it('should error reconnecting a div with different text dangerouslySetInnerHTML', () =>
|
||||
expectMarkupMismatch(
|
||||
<div dangerouslySetInnerHTML={{__html: 'foo'}} />,
|
||||
<div dangerouslySetInnerHTML={{__html: 'bar'}} />,
|
||||
));
|
||||
|
||||
it('should error reconnecting a div with different number dangerouslySetInnerHTML', () =>
|
||||
expectMarkupMismatch(
|
||||
<div dangerouslySetInnerHTML={{__html: 10}} />,
|
||||
<div dangerouslySetInnerHTML={{__html: 20}} />,
|
||||
));
|
||||
|
||||
it('should error reconnecting a div with different object dangerouslySetInnerHTML', () =>
|
||||
expectMarkupMismatch(
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: {
|
||||
toString() {
|
||||
return 'hi';
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: {
|
||||
toString() {
|
||||
return 'bye';
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
));
|
||||
|
||||
it('can explicitly ignore reconnecting a div with different dangerouslySetInnerHTML', () =>
|
||||
expectMarkupMatch(
|
||||
<div dangerouslySetInnerHTML={{__html: "<span id='child1'/>"}} />,
|
||||
|
||||
+5
-2
@@ -987,9 +987,12 @@ export function diffHydratedProperties(
|
||||
) {
|
||||
// Noop
|
||||
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
|
||||
const rawHtml = nextProp ? nextProp[HTML] || '' : '';
|
||||
const serverHTML = domElement.innerHTML;
|
||||
const expectedHTML = normalizeHTML(domElement, rawHtml);
|
||||
const nextHtml = nextProp ? nextProp[HTML] : undefined;
|
||||
const expectedHTML = normalizeHTML(
|
||||
domElement,
|
||||
nextHtml != null ? nextHtml : '',
|
||||
);
|
||||
if (expectedHTML !== serverHTML) {
|
||||
warnForPropDifference(propKey, serverHTML, expectedHTML);
|
||||
}
|
||||
|
||||
+1
-1
@@ -249,7 +249,7 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
|
||||
typeof props.children === 'number' ||
|
||||
(typeof props.dangerouslySetInnerHTML === 'object' &&
|
||||
props.dangerouslySetInnerHTML !== null &&
|
||||
typeof props.dangerouslySetInnerHTML.__html === 'string')
|
||||
props.dangerouslySetInnerHTML.__html != null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user