/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
describe('ReactDOMComponent', () => {
let React;
let ReactTestUtils;
let ReactDOM;
let ReactDOMServer;
function normalizeCodeLocInfo(str) {
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
}
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
});
describe('updateDOM', () => {
it('should handle className', () => {
const container = document.createElement('div');
ReactDOM.render(
, container);
ReactDOM.render(, container);
expect(container.firstChild.className).toEqual('foo');
ReactDOM.render(, container);
expect(container.firstChild.className).toEqual('bar');
ReactDOM.render(, container);
expect(container.firstChild.className).toEqual('');
});
it('should gracefully handle various style value types', () => {
const container = document.createElement('div');
ReactDOM.render(, container);
const stubStyle = container.firstChild.style;
// set initial style
const setup = {
display: 'block',
left: '1px',
top: 2,
fontFamily: 'Arial',
};
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('block');
expect(stubStyle.left).toEqual('1px');
expect(stubStyle.top).toEqual('2px');
expect(stubStyle.fontFamily).toEqual('Arial');
// reset the style to their default state
const reset = {display: '', left: null, top: false, fontFamily: true};
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('');
expect(stubStyle.left).toEqual('');
expect(stubStyle.top).toEqual('');
expect(stubStyle.fontFamily).toEqual('');
});
it('should not update styles when mutating a proxy style object', () => {
const styleStore = {
display: 'none',
fontFamily: 'Arial',
lineHeight: 1.2,
};
// We use a proxy style object so that we can mutate it even if it is
// frozen in DEV.
const styles = {
get display() {
return styleStore.display;
},
set display(v) {
styleStore.display = v;
},
get fontFamily() {
return styleStore.fontFamily;
},
set fontFamily(v) {
styleStore.fontFamily = v;
},
get lineHeight() {
return styleStore.lineHeight;
},
set lineHeight(v) {
styleStore.lineHeight = v;
},
};
const container = document.createElement('div');
ReactDOM.render(, container);
const stubStyle = container.firstChild.style;
stubStyle.display = styles.display;
stubStyle.fontFamily = styles.fontFamily;
styles.display = 'block';
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('none');
expect(stubStyle.fontFamily).toEqual('Arial');
expect(stubStyle.lineHeight).toEqual('1.2');
styles.fontFamily = 'Helvetica';
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('none');
expect(stubStyle.fontFamily).toEqual('Arial');
expect(stubStyle.lineHeight).toEqual('1.2');
styles.lineHeight = 0.5;
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('none');
expect(stubStyle.fontFamily).toEqual('Arial');
expect(stubStyle.lineHeight).toEqual('1.2');
ReactDOM.render(, container);
expect(stubStyle.display).toBe('');
expect(stubStyle.fontFamily).toBe('');
expect(stubStyle.lineHeight).toBe('');
});
it('should throw when mutating style objects', () => {
const style = {border: '1px solid black'};
class App extends React.Component {
state = {style: style};
render() {
return
{}} />, container),
).toWarnDev(
'Warning: Invalid value for prop `foo` on
tag. Either remove it ' +
'from the element, or pass a string or number value to keep ' +
'it in the DOM. For details, see https://fb.me/react-attribute-behavior' +
'\n in div (at **)',
);
});
it('should group multiple unknown prop warnings together', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(
{}} baz={() => {}} />, container),
).toWarnDev(
'Warning: Invalid values for props `foo`, `baz` on
tag. Either remove ' +
'them from the element, or pass a string or number value to keep ' +
'them in the DOM. For details, see https://fb.me/react-attribute-behavior' +
'\n in div (at **)',
);
});
it('should warn for onDblClick prop', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(
{}} />, container),
).toWarnDev(
'Warning: Invalid event handler property `onDblClick`. Did you mean `onDoubleClick`?\n in div (at **)',
);
});
it('should warn for unknown string event handlers', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(, container),
).toWarnDev(
'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
);
expect(container.firstChild.hasAttribute('onUnknown')).toBe(false);
expect(container.firstChild.onUnknown).toBe(undefined);
expect(() =>
ReactDOM.render(, container),
).toWarnDev(
'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
);
expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
expect(container.firstChild.onunknown).toBe(undefined);
expect(() =>
ReactDOM.render(
,
container,
),
).toWarnDev(
'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
);
expect(container.firstChild.hasAttribute('on-unknown')).toBe(false);
expect(container.firstChild['on-unknown']).toBe(undefined);
});
it('should warn for unknown function event handlers', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(, container),
).toWarnDev(
'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
);
expect(container.firstChild.hasAttribute('onUnknown')).toBe(false);
expect(container.firstChild.onUnknown).toBe(undefined);
expect(() =>
ReactDOM.render(, container),
).toWarnDev(
'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
);
expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
expect(container.firstChild.onunknown).toBe(undefined);
expect(() =>
ReactDOM.render(, container),
).toWarnDev(
'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
);
expect(container.firstChild.hasAttribute('on-unknown')).toBe(false);
expect(container.firstChild['on-unknown']).toBe(undefined);
});
it('should warn for badly cased React attributes', () => {
const container = document.createElement('div');
expect(() => ReactDOM.render(, container)).toWarnDev(
'Warning: Invalid DOM property `CHILDREN`. Did you mean `children`?\n in div (at **)',
);
expect(container.firstChild.getAttribute('CHILDREN')).toBe('5');
});
it('should not warn for "0" as a unitless style value', () => {
class Component extends React.Component {
render() {
return ;
}
}
ReactTestUtils.renderIntoDocument();
});
it('should warn nicely about NaN in style', () => {
const style = {fontSize: NaN};
const div = document.createElement('div');
expect(() => ReactDOM.render(, div)).toWarnDev(
'Warning: `NaN` is an invalid value for the `fontSize` css style property.' +
'\n in span (at **)',
);
ReactDOM.render(, div);
});
it('should update styles if initially null', () => {
let styles = null;
const container = document.createElement('div');
ReactDOM.render(, container);
const stubStyle = container.firstChild.style;
styles = {display: 'block'};
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('block');
});
it('should update styles if updated to null multiple times', () => {
let styles = null;
const container = document.createElement('div');
ReactDOM.render(, container);
styles = {display: 'block'};
const stubStyle = container.firstChild.style;
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('block');
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('');
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('block');
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('');
});
it('should allow named slot projection on both web components and regular DOM elements', () => {
const container = document.createElement('div');
ReactDOM.render(
,
container,
);
const lightDOM = container.firstChild.childNodes;
expect(lightDOM[0].getAttribute('slot')).toBe('first');
expect(lightDOM[1].getAttribute('slot')).toBe('second');
});
it('should skip reserved props on web components', () => {
const container = document.createElement('div');
ReactDOM.render(
,
container,
);
expect(container.firstChild.hasAttribute('children')).toBe(false);
expect(
container.firstChild.hasAttribute('suppressContentEditableWarning'),
).toBe(false);
expect(
container.firstChild.hasAttribute('suppressHydrationWarning'),
).toBe(false);
ReactDOM.render(
,
container,
);
expect(container.firstChild.hasAttribute('children')).toBe(false);
expect(
container.firstChild.hasAttribute('suppressContentEditableWarning'),
).toBe(false);
expect(
container.firstChild.hasAttribute('suppressHydrationWarning'),
).toBe(false);
});
it('should skip dangerouslySetInnerHTML on web components', () => {
const container = document.createElement('div');
ReactDOM.render(
,
container,
);
expect(container.firstChild.hasAttribute('dangerouslySetInnerHTML')).toBe(
false,
);
ReactDOM.render(
,
container,
);
expect(container.firstChild.hasAttribute('dangerouslySetInnerHTML')).toBe(
false,
);
});
it('should render null and undefined as empty but print other falsy values', () => {
const container = document.createElement('div');
ReactDOM.render(
,
container,
);
expect(container.textContent).toEqual('textContent');
ReactDOM.render(, container);
expect(container.textContent).toEqual('0');
ReactDOM.render(
,
container,
);
expect(container.textContent).toEqual('false');
ReactDOM.render(
,
container,
);
expect(container.textContent).toEqual('');
ReactDOM.render(
,
container,
);
expect(container.textContent).toEqual('');
ReactDOM.render(
,
container,
);
expect(container.textContent).toEqual('');
});
it('should remove attributes', () => {
const container = document.createElement('div');
ReactDOM.render(, container);
expect(container.firstChild.hasAttribute('height')).toBe(true);
ReactDOM.render(, container);
expect(container.firstChild.hasAttribute('height')).toBe(false);
});
it('should remove properties', () => {
const container = document.createElement('div');
ReactDOM.render(, container);
expect(container.firstChild.className).toEqual('monkey');
ReactDOM.render(, container);
expect(container.firstChild.className).toEqual('');
});
it('should not set null/undefined attributes', () => {
const container = document.createElement('div');
// Initial render.
ReactDOM.render(, container);
const node = container.firstChild;
expect(node.hasAttribute('src')).toBe(false);
expect(node.hasAttribute('data-foo')).toBe(false);
// Update in one direction.
ReactDOM.render(, container);
expect(node.hasAttribute('src')).toBe(false);
expect(node.hasAttribute('data-foo')).toBe(false);
// Update in another direction.
ReactDOM.render(, container);
expect(node.hasAttribute('src')).toBe(false);
expect(node.hasAttribute('data-foo')).toBe(false);
// Removal.
ReactDOM.render(, container);
expect(node.hasAttribute('src')).toBe(false);
expect(node.hasAttribute('data-foo')).toBe(false);
// Addition.
ReactDOM.render(, container);
expect(node.hasAttribute('src')).toBe(false);
expect(node.hasAttribute('data-foo')).toBe(false);
});
it('should apply React-specific aliases to HTML elements', () => {
const container = document.createElement('div');
ReactDOM.render(, container);
const node = container.firstChild;
// Test attribute initialization.
expect(node.getAttribute('accept-charset')).toBe('foo');
expect(node.hasAttribute('acceptCharset')).toBe(false);
// Test attribute update.
ReactDOM.render(, container);
expect(node.getAttribute('accept-charset')).toBe('boo');
expect(node.hasAttribute('acceptCharset')).toBe(false);
// Test attribute removal by setting to null.
ReactDOM.render(, container);
expect(node.hasAttribute('accept-charset')).toBe(false);
expect(node.hasAttribute('acceptCharset')).toBe(false);
// Restore.
ReactDOM.render(, container);
expect(node.getAttribute('accept-charset')).toBe('foo');
expect(node.hasAttribute('acceptCharset')).toBe(false);
// Test attribute removal by setting to undefined.
ReactDOM.render(, container);
expect(node.hasAttribute('accept-charset')).toBe(false);
expect(node.hasAttribute('acceptCharset')).toBe(false);
// Restore.
ReactDOM.render(, container);
expect(node.getAttribute('accept-charset')).toBe('foo');
expect(node.hasAttribute('acceptCharset')).toBe(false);
// Test attribute removal.
ReactDOM.render(, container);
expect(node.hasAttribute('accept-charset')).toBe(false);
expect(node.hasAttribute('acceptCharset')).toBe(false);
});
it('should apply React-specific aliases to SVG elements', () => {
const container = document.createElement('div');
ReactDOM.render(, container);
const node = container.firstChild;
// Test attribute initialization.
expect(node.getAttribute('arabic-form')).toBe('foo');
expect(node.hasAttribute('arabicForm')).toBe(false);
// Test attribute update.
ReactDOM.render(, container);
expect(node.getAttribute('arabic-form')).toBe('boo');
expect(node.hasAttribute('arabicForm')).toBe(false);
// Test attribute removal by setting to null.
ReactDOM.render(, container);
expect(node.hasAttribute('arabic-form')).toBe(false);
expect(node.hasAttribute('arabicForm')).toBe(false);
// Restore.
ReactDOM.render(, container);
expect(node.getAttribute('arabic-form')).toBe('foo');
expect(node.hasAttribute('arabicForm')).toBe(false);
// Test attribute removal by setting to undefined.
ReactDOM.render(, container);
expect(node.hasAttribute('arabic-form')).toBe(false);
expect(node.hasAttribute('arabicForm')).toBe(false);
// Restore.
ReactDOM.render(, container);
expect(node.getAttribute('arabic-form')).toBe('foo');
expect(node.hasAttribute('arabicForm')).toBe(false);
// Test attribute removal.
ReactDOM.render(, container);
expect(node.hasAttribute('arabic-form')).toBe(false);
expect(node.hasAttribute('arabicForm')).toBe(false);
});
it('should properly update custom attributes on custom elements', () => {
const container = document.createElement('div');
ReactDOM.render(, container);
ReactDOM.render(, container);
const node = container.firstChild;
expect(node.hasAttribute('foo')).toBe(false);
expect(node.getAttribute('bar')).toBe('buzz');
});
it('should not apply React-specific aliases to custom elements', () => {
const container = document.createElement('div');
ReactDOM.render(, container);
const node = container.firstChild;
// Should not get transformed to arabic-form as SVG would be.
expect(node.getAttribute('arabicForm')).toBe('foo');
expect(node.hasAttribute('arabic-form')).toBe(false);
// Test attribute update.
ReactDOM.render(, container);
expect(node.getAttribute('arabicForm')).toBe('boo');
// Test attribute removal and addition.
ReactDOM.render(, container);
// Verify the previous attribute was removed.
expect(node.hasAttribute('arabicForm')).toBe(false);
// Should not get transformed to accept-charset as HTML would be.
expect(node.getAttribute('acceptCharset')).toBe('buzz');
expect(node.hasAttribute('accept-charset')).toBe(false);
});
it('should clear a single style prop when changing `style`', () => {
let styles = {display: 'none', color: 'red'};
const container = document.createElement('div');
ReactDOM.render(, container);
const stubStyle = container.firstChild.style;
styles = {color: 'green'};
ReactDOM.render(, container);
expect(stubStyle.display).toEqual('');
expect(stubStyle.color).toEqual('green');
});
it('should reject attribute key injection attack on markup for regular DOM (SSR)', () => {
expect(() => {
for (let i = 0; i < 3; i++) {
const element1 = React.createElement(
'div',
{'blah" onclick="beevil" noise="hi': 'selected'},
null,
);
const element2 = React.createElement(
'div',
{'>