packages/render/dom/__tests__/ReactDOMFiber-test.js
'use strict';
const React = require('react');
const ReactDOM = require('react-dom');
const ReactTestUtils = require('test-utils');
const PropTypes = require('prop-types');
describe('ReactDOMFiber', () => {
let container;
beforeEach(() => {
container = document.createElement('div');
});
afterEach(() => {
ReactDOM.unmountComponentAtNode(container);
}, 5000);
it('should render strings as children', () => {
const Box = ({ value }) => <div>{value}</div>;
ReactDOM.render(<Box value="foo" />, container);
expect(container.textContent).toEqual('foo');
});
it('should render numbers as children', () => {
const Box = ({ value }) => <div>{value}</div>;
ReactDOM.render(<Box value={10} />, container);
expect(container.textContent).toEqual('10');
});
it('should be called a callback argument', () => {
// mounting phase
let called = false;
ReactDOM.render(<div>Foo</div>, container, () => (called = true));
expect(called).toEqual(true);
// updating phase
called = false;
ReactDOM.render(<div>Foo</div>, container, () => (called = true));
expect(called).toEqual(true);
});
it('should call a callback argument when the same element is re-rendered', () => {
class Foo extends React.Component {
render() {
return <div>Foo</div>;
}
}
const element = <Foo />;
// mounting phase
let called = false;
ReactDOM.render(element, container, () => (called = true));
expect(called).toEqual(true);
// updating phase
called = false;
ReactDOM.unstable_batchedUpdates(() => {
ReactDOM.render(element, container, () => (called = true));
});
expect(called).toEqual(true);
});
it('should render a component returning strings directly from render', () => {
const Text = ({ value }) => value;
ReactDOM.render(<Text value="foo" />, container);
expect(container.textContent).toEqual('foo');
});
it('should render a component returning numbers directly from render', () => {
const Text = ({ value }) => value;
ReactDOM.render(<Text value={10} />, container);
expect(container.textContent).toEqual('10');
});
it('finds the DOM Text node of a string child', () => {
class Text extends React.Component {
render() {
return this.props.value;
}
}
let instance = null;
ReactDOM.render(<Text value="foo" ref={ref => (instance = ref)} />, container);
const textNode = ReactDOM.findDOMNode(instance);
expect(textNode).toBe(container.firstChild);
expect(textNode.nodeType).toBe(3);
expect(textNode.nodeValue).toBe('foo');
});
it('finds the first child when a component returns a fragment', () => {
class Fragment extends React.Component {
render() {
return [<div key="a" />, <span key="b" />];
}
}
let instance = null;
ReactDOM.render(<Fragment ref={ref => (instance = ref)} />, container);
expect(container.childNodes.length).toBe(2);
const firstNode = ReactDOM.findDOMNode(instance);
expect(firstNode).toBe(container.firstChild);
expect(firstNode.tagName).toBe('DIV');
});
it('finds the first child even when fragment is nested', () => {
class Wrapper extends React.Component {
render() {
return this.props.children;
}
}
class Fragment extends React.Component {
render() {
return [
<Wrapper key="a">
<div />
</Wrapper>,
<span key="b" />,
];
}
}
let instance = null;
ReactDOM.render(<Fragment ref={ref => (instance = ref)} />, container);
expect(container.childNodes.length).toBe(2);
const firstNode = ReactDOM.findDOMNode(instance);
expect(firstNode).toBe(container.firstChild);
expect(firstNode.tagName).toBe('DIV');
});
it('finds the first child even when first child renders null', () => {
class NullComponent extends React.Component {
render() {
return null;
}
}
class Fragment extends React.Component {
render() {
return [<NullComponent key="a" />, <div key="b" />, <span key="c" />];
}
}
let instance = null;
ReactDOM.render(<Fragment ref={ref => (instance = ref)} />, container);
expect(container.childNodes.length).toBe(2);
const firstNode = ReactDOM.findDOMNode(instance);
expect(firstNode).toBe(container.firstChild);
expect(firstNode.tagName).toBe('DIV');
});
let svgEls, htmlEls, mathEls;
const expectSVG = { ref: el => svgEls.push(el) };
const expectHTML = { ref: el => htmlEls.push(el) };
const expectMath = { ref: el => mathEls.push(el) };
const usePortal = function(tree) {
return ReactDOM.createPortal(tree, document.createElement('div'));
};
const assertNamespacesMatch = function(tree) {
container = document.createElement('div');
svgEls = [];
htmlEls = [];
mathEls = [];
ReactDOM.render(tree, container);
svgEls.forEach(el => {
expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg');
});
htmlEls.forEach(el => {
expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
});
mathEls.forEach(el => {
expect(el.namespaceURI).toBe('http://www.w3.org/1998/Math/MathML');
});
ReactDOM.unmountComponentAtNode(container);
expect(container.innerHTML).toBe('');
};
it('should render one portal', () => {
const portalContainer = document.createElement('div');
ReactDOM.render(<div>{ReactDOM.createPortal(<div>portal</div>, portalContainer)}</div>, container);
expect(portalContainer.innerHTML).toBe('<div>portal</div>');
expect(container.innerHTML).toBe('<div></div>');
ReactDOM.unmountComponentAtNode(container);
expect(portalContainer.innerHTML).toBe('');
expect(container.innerHTML).toBe('');
});
// TODO: remove in React 17
it('should support unstable_createPortal alias', () => {
const portalContainer = document.createElement('div');
const createPortal = ReactDOM.createPortal;
expect(() =>
ReactDOM.render(<div>{createPortal(<div>portal</div>, portalContainer)}</div>, container)
).toLowPriorityWarnDev(
'The ReactDOM.unstable_createPortal() alias has been deprecated, ' +
'and will be removed in React 17+. Update your code to use ' +
'ReactDOM.createPortal() instead. It has the exact same API, ' +
'but without the "unstable_" prefix.'
);
expect(portalContainer.innerHTML).toBe('<div>portal</div>');
expect(container.innerHTML).toBe('<div></div>');
ReactDOM.unmountComponentAtNode(container);
expect(portalContainer.innerHTML).toBe('');
expect(container.innerHTML).toBe('');
});
it('should render many portals', () => {
const portalContainer1 = document.createElement('div');
const portalContainer2 = document.createElement('div');
const ops = [];
class Child extends React.Component {
componentDidMount() {
ops.push(`${this.props.name} componentDidMount`);
}
componentDidUpdate() {
ops.push(`${this.props.name} componentDidUpdate`);
}
componentWillUnmount() {
ops.push(`${this.props.name} componentWillUnmount`);
}
render() {
return <div>{this.props.name}</div>;
}
}
class Parent extends React.Component {
componentDidMount() {
ops.push(`Parent:${this.props.step} componentDidMount`);
}
componentDidUpdate() {
ops.push(`Parent:${this.props.step} componentDidUpdate`);
}
componentWillUnmount() {
ops.push(`Parent:${this.props.step} componentWillUnmount`);
}
render() {
const { step } = this.props;
return [
<Child key="a" name={`normal[0]:${step}`} />,
ReactDOM.createPortal(<Child key="b" name={`portal1[0]:${step}`} />, portalContainer1),
<Child key="c" name={`normal[1]:${step}`} />,
ReactDOM.createPortal(
[<Child key="d" name={`portal2[0]:${step}`} />, <Child key="e" name={`portal2[1]:${step}`} />],
portalContainer2
),
];
}
}
ReactDOM.render(<Parent step="a" />, container);
expect(portalContainer1.innerHTML).toBe('<div>portal1[0]:a</div>');
expect(portalContainer2.innerHTML).toBe('<div>portal2[0]:a</div><div>portal2[1]:a</div>');
expect(container.innerHTML).toBe('<div>normal[0]:a</div><div>normal[1]:a</div>');
expect(ops).toEqual([
'normal[0]:a componentDidMount',
'portal1[0]:a componentDidMount',
'normal[1]:a componentDidMount',
'portal2[0]:a componentDidMount',
'portal2[1]:a componentDidMount',
'Parent:a componentDidMount',
]);
ops.length = 0;
ReactDOM.render(<Parent step="b" />, container);
expect(portalContainer1.innerHTML).toBe('<div>portal1[0]:b</div>');
expect(portalContainer2.innerHTML).toBe('<div>portal2[0]:b</div><div>portal2[1]:b</div>');
expect(container.innerHTML).toBe('<div>normal[0]:b</div><div>normal[1]:b</div>');
expect(ops).toEqual([
'normal[0]:b componentDidUpdate',
'portal1[0]:b componentDidUpdate',
'normal[1]:b componentDidUpdate',
'portal2[0]:b componentDidUpdate',
'portal2[1]:b componentDidUpdate',
'Parent:b componentDidUpdate',
]);
ops.length = 0;
ReactDOM.unmountComponentAtNode(container);
expect(portalContainer1.innerHTML).toBe('');
expect(portalContainer2.innerHTML).toBe('');
expect(container.innerHTML).toBe('');
expect(ops).toEqual([
'Parent:b componentWillUnmount',
'normal[0]:b componentWillUnmount',
'portal1[0]:b componentWillUnmount',
'normal[1]:b componentWillUnmount',
'portal2[0]:b componentWillUnmount',
'portal2[1]:b componentWillUnmount',
]);
});
it("复杂的插入点机制测试", () => {
function prev(el){
return el.previousSibling || {}
}
var div = document.createElement("div")
class InnerBox extends React.PureComponent {
state = {
current: 0,
}
componentDidMount() {
console.log('InnerBox render');
setTimeout(() => {
console.log('current跟上次不一样)');
this.setState({ current: 1 }, ()=>{
expect(prev(this.refs.aaa).id).toBe("a2")
});
}, 50);
setTimeout(() => {
console.log('current跟上次一样,dom位置互换');
this.setState({ current: 1 }, ()=>{
expect(prev(this.refs.aaa).id).toBe("a2")
});
}, 100);
setTimeout(() => {
console.log('current跟上次不一样,dom位置复原');
this.setState({ current: 2 }, ()=>{
expect(prev(this.refs.aaa).id).toBe("a2")
});
}, 150);
setTimeout(() => {
console.log('current跟上次一样,dom位置互换');
this.setState({ current: 2 }, ()=>{
expect(prev(this.refs.aaa).id).toBe("a2")
});
}, 200);
}
render() {
console.log('inner render trigger');
return (
<div id="a3" ref="aaa">
<div style={{ height: 100 }}>内部组件{this.state.current}</div>
</div>
);
}
};
class WrapBox extends React.PureComponent {
state = {
value: 1,
}
componentDidMount() {
console.log('WrapBox render');
// 此处如果在组件创建以后,再次setState,会导致dom位置错误的现象发生
setTimeout(() => {
console.log('父容器state值改变');
this.setState({
value: 1,
});
}, 70);
}
render() {
console.log('wrap render trigger');
return (
<quoteblock>
<div id="a1">标题区域1</div>
<div id="a2">标题区域2</div>
<InnerBox />
<div id="a4">标题区域3</div>
</quoteblock>
);
}
}
ReactDOM.render(
<div>
<WrapBox />
<div>hello anu!</div>
</div>,
div
);
})
it('should render nested portals', () => {
const portalContainer1 = document.createElement('div');
const portalContainer2 = document.createElement('div');
const portalContainer3 = document.createElement('div');
ReactDOM.render(
[
<div key="a">normal[0]</div>,
ReactDOM.createPortal(
[
<div key="b">portal1[0]</div>,
ReactDOM.createPortal(<div key="c">portal2[0]</div>, portalContainer2),
ReactDOM.createPortal(<div key="d">portal3[0]</div>, portalContainer3),
<div key="e">portal1[1]</div>,
],
portalContainer1
),
<div key="f">normal[1]</div>,
],
container
);
expect(portalContainer1.innerHTML).toBe('<div>portal1[0]</div><div>portal1[1]</div>');
expect(portalContainer2.innerHTML).toBe('<div>portal2[0]</div>');
expect(portalContainer3.innerHTML).toBe('<div>portal3[0]</div>');
expect(container.innerHTML).toBe('<div>normal[0]</div><div>normal[1]</div>');
ReactDOM.unmountComponentAtNode(container);
expect(portalContainer1.innerHTML).toBe('');
expect(portalContainer2.innerHTML).toBe('');
expect(portalContainer3.innerHTML).toBe('');
expect(container.innerHTML).toBe('');
});
it('should reconcile portal children', () => {
const portalContainer = document.createElement('div');
ReactDOM.render(<div>{ReactDOM.createPortal(<div>portal:1</div>, portalContainer)}</div>, container);
expect(portalContainer.innerHTML).toBe('<div>portal:1</div>');
expect(container.innerHTML).toBe('<div></div>');
ReactDOM.render(<div>{ReactDOM.createPortal(<div>portal:2</div>, portalContainer)}</div>, container);
expect(portalContainer.innerHTML).toBe('<div>portal:2</div>');
expect(container.innerHTML).toBe('<div></div>');
ReactDOM.render(<div>{ReactDOM.createPortal(<p>portal:3</p>, portalContainer)}</div>, container);
expect(portalContainer.innerHTML).toBe('<p>portal:3</p>');
expect(container.innerHTML).toBe('<div></div>');
ReactDOM.render(<div>{ReactDOM.createPortal(['Hi', 'Bye'], portalContainer)}</div>, container);
expect(portalContainer.innerHTML).toBe('HiBye');
expect(container.innerHTML).toBe('<div></div>');
ReactDOM.render(<div>{ReactDOM.createPortal(['Bye', 'Hi'], portalContainer)}</div>, container);
expect(portalContainer.innerHTML).toBe('ByeHi');
expect(container.innerHTML).toBe('<div></div>');
ReactDOM.render(<div>{ReactDOM.createPortal(null, portalContainer)}</div>, container);
expect(portalContainer.innerHTML).toBe('');
expect(container.innerHTML).toBe('<div></div>');
});
it('should keep track of namespace across portals (simple)', () => {
assertNamespacesMatch(
<svg {...expectSVG}>
<image {...expectSVG} />
{usePortal(<div {...expectHTML} />)}
<image {...expectSVG} />
</svg>
);
assertNamespacesMatch(
<math {...expectMath}>
<mi {...expectMath} />
{usePortal(<div {...expectHTML} />)}
<mi {...expectMath} />
</math>
);
assertNamespacesMatch(
<div {...expectHTML}>
<p {...expectHTML} />
{usePortal(
<svg {...expectSVG}>
<image {...expectSVG} />
</svg>
)}
<p {...expectHTML} />
</div>
);
});
it('should keep track of namespace across portals (medium)', () => {
assertNamespacesMatch(
<svg {...expectSVG}>
<image {...expectSVG} />
{usePortal(<div {...expectHTML} />)}
<image {...expectSVG} />
{usePortal(<div {...expectHTML} />)}
<image {...expectSVG} />
</svg>
);
assertNamespacesMatch(
<div {...expectHTML}>
<math {...expectMath}>
<mi {...expectMath} />
{usePortal(
<svg {...expectSVG}>
<image {...expectSVG} />
</svg>
)}
</math>
<p {...expectHTML} />
</div>
);
assertNamespacesMatch(
<math {...expectMath}>
<mi {...expectMath} />
{usePortal(
<svg {...expectSVG}>
<image {...expectSVG} />
<foreignObject {...expectSVG}>
<p {...expectHTML} />
<math {...expectMath}>
<mi {...expectMath} />
</math>
<p {...expectHTML} />
</foreignObject>
<image {...expectSVG} />
</svg>
)}
<mi {...expectMath} />
</math>
);
assertNamespacesMatch(
<div {...expectHTML}>
{usePortal(
<svg {...expectSVG}>
{usePortal(<div {...expectHTML} />)}
<image {...expectSVG} />
</svg>
)}
<p {...expectHTML} />
</div>
);
assertNamespacesMatch(
<svg {...expectSVG}>
<svg {...expectSVG}>
{usePortal(<div {...expectHTML} />)}
<image {...expectSVG} />
</svg>
<image {...expectSVG} />
</svg>
);
});
it('should keep track of namespace across portals (complex)', () => {
assertNamespacesMatch(
<div {...expectHTML}>
{usePortal(
<svg {...expectSVG}>
<image {...expectSVG} />
</svg>
)}
<p {...expectHTML} />
<svg {...expectSVG}>
<image {...expectSVG} />
</svg>
<svg {...expectSVG}>
<svg {...expectSVG}>
<image {...expectSVG} />
</svg>
<image {...expectSVG} />
</svg>
<p {...expectHTML} />
</div>
);
assertNamespacesMatch(
<div {...expectHTML}>
<svg {...expectSVG}>
<svg {...expectSVG}>
<image {...expectSVG} />
{usePortal(
<svg {...expectSVG}>
<image {...expectSVG} />
<svg {...expectSVG}>
<image {...expectSVG} />
</svg>
<image {...expectSVG} />
</svg>
)}
<image {...expectSVG} />
<foreignObject {...expectSVG}>
<p {...expectHTML} />
{usePortal(<p {...expectHTML} />)}
<p {...expectHTML} />
</foreignObject>
</svg>
<image {...expectSVG} />
</svg>
<p {...expectHTML} />
</div>
);
assertNamespacesMatch(
<div {...expectHTML}>
<svg {...expectSVG}>
<foreignObject {...expectSVG}>
<p {...expectHTML} />
{usePortal(
<svg {...expectSVG}>
<image {...expectSVG} />
<svg {...expectSVG}>
<image {...expectSVG} />
<foreignObject {...expectSVG}>
<p {...expectHTML} />
</foreignObject>
{usePortal(<p {...expectHTML} />)}
</svg>
<image {...expectSVG} />
</svg>
)}
<p {...expectHTML} />
</foreignObject>
<image {...expectSVG} />
</svg>
<p {...expectHTML} />
</div>
);
});
it('should unwind namespaces on uncaught errors', () => {
function BrokenRender() {
throw new Error('Hello');
}
expect(() => {
assertNamespacesMatch(
<svg {...expectSVG}>
<BrokenRender />
</svg>
);
}).toThrow('Hello');
assertNamespacesMatch(<div {...expectHTML} />);
});
it('should unwind namespaces on caught errors', () => {
function BrokenRender() {
throw new Error('Hello');
}
class ErrorBoundary extends React.Component {
state = { error: null };
componentDidCatch(error) {
this.setState({ error });
}
render() {
if (this.state.error) {
return <p {...expectHTML} />;
}
return this.props.children;
}
}
assertNamespacesMatch(
<svg {...expectSVG}>
<foreignObject {...expectSVG}>
<ErrorBoundary>
<math {...expectMath}>
<BrokenRender />
</math>
</ErrorBoundary>
</foreignObject>
<image {...expectSVG} />
</svg>
);
assertNamespacesMatch(<div {...expectHTML} />);
});
it('should unwind namespaces on caught errors in a portal', () => {
function BrokenRender() {
throw new Error('Hello');
}
class ErrorBoundary extends React.Component {
state = { error: null };
componentDidCatch(error) {
this.setState({ error });
}
render() {
if (this.state.error) {
return <image {...expectSVG} />;
}
return this.props.children;
}
}
assertNamespacesMatch(
<svg {...expectSVG}>
<ErrorBoundary>
{usePortal(
<div {...expectHTML}>
<math {...expectMath}>
<BrokenRender />)
</math>
</div>
)}
</ErrorBoundary>
{usePortal(<div {...expectHTML} />)}
</svg>
);
});
it('should pass portal context when rendering subtree elsewhere', () => {
const portalContainer = document.createElement('div');
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
};
render() {
return <div>{this.context.foo}</div>;
}
}
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
};
getChildContext() {
return {
foo: 'bar',
};
}
render() {
return ReactDOM.createPortal(<Component />, portalContainer);
}
}
ReactDOM.render(<Parent />, container);
expect(container.innerHTML).toBe('');
expect(portalContainer.innerHTML).toBe('<div>bar</div>');
});
it('should update portal context if it changes due to setState', () => {
const portalContainer = document.createElement('div');
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
render() {
return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
}
}
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
state = {
bar: 'initial',
};
getChildContext() {
return {
foo: this.state.bar,
getFoo: () => this.state.bar,
};
}
render() {
return ReactDOM.createPortal(<Component />, portalContainer);
}
}
const instance = ReactDOM.render(<Parent />, container);
expect(portalContainer.innerHTML).toBe('<div>initial-initial</div>');
expect(container.innerHTML).toBe('');
instance.setState({ bar: 'changed' });
expect(portalContainer.innerHTML).toBe('<div>changed-changed</div>');
expect(container.innerHTML).toBe('');
});
it('should update portal context if it changes due to re-render', () => {
const portalContainer = document.createElement('div');
class Component extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
render() {
return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
}
}
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
getFoo: PropTypes.func.isRequired,
};
getChildContext() {
return {
foo: this.props.bar,
getFoo: () => this.props.bar,
};
}
render() {
return ReactDOM.createPortal(<Component />, portalContainer);
}
}
ReactDOM.render(<Parent bar="initial" />, container);
expect(portalContainer.innerHTML).toBe('<div>initial-initial</div>');
expect(container.innerHTML).toBe('');
ReactDOM.render(<Parent bar="changed" />, container);
expect(portalContainer.innerHTML).toBe('<div>changed-changed</div>');
expect(container.innerHTML).toBe('');
});
it('findDOMNode should find dom element after expanding a fragment', () => {
class MyNode extends React.Component {
render() {
return !this.props.flag ? [<div key="a" />] : [<span key="b" />, <div key="a" />];
}
}
const myNodeA = ReactDOM.render(<MyNode />, container);
const a = ReactDOM.findDOMNode(myNodeA);
expect(a.tagName).toBe('DIV');
const myNodeB = ReactDOM.render(<MyNode flag={true} />, container);
expect(myNodeA === myNodeB).toBe(true);
const b = ReactDOM.findDOMNode(myNodeB);
expect(b.tagName).toBe('SPAN');
});
it('should bubble events from the portal to the parent', () => {
const portalContainer = document.createElement('div');
const ops = [];
let portal = null;
ReactDOM.render(
<div onClick={() => ops.push('parent clicked')}>
{ReactDOM.createPortal(
<div onClick={() => ops.push('portal clicked')} ref={n => (portal = n)}>
portal
</div>,
portalContainer
)}
</div>,
container
);
expect(portal.tagName).toBe('DIV');
const fakeNativeEvent = {};
ReactTestUtils.simulateNativeEventOnNode('topClick', portal, fakeNativeEvent);
expect(ops).toEqual(['portal clicked', 'parent clicked']);
});
it('should not onMouseLeave when staying in the portal', () => {
console.log('现在无法模仿onMouseLeave');
return;
const portalContainer = document.createElement('div');
let ops = [];
let firstTarget = null;
let secondTarget = null;
let thirdTarget = null;
function simulateMouseMove(from, to) {
if (from) {
ReactTestUtils.simulateNativeEventOnNode('topMouseOut', from, {
target: from,
relatedTarget: to,
});
}
if (to) {
ReactTestUtils.simulateNativeEventOnNode('topMouseOver', to, {
target: to,
relatedTarget: from,
});
}
}
ReactDOM.render(
<div>
<div onMouseEnter={() => ops.push('enter parent')} onMouseLeave={() => ops.push('leave parent')}>
<div ref={n => (firstTarget = n)} />
{ReactDOM.createPortal(
<div
onMouseEnter={() => ops.push('enter portal')}
onMouseLeave={() => ops.push('leave portal')}
ref={n => (secondTarget = n)}
>
portal
</div>,
portalContainer
)}
</div>
<div ref={n => (thirdTarget = n)} />
</div>,
container
);
simulateMouseMove(null, firstTarget);
expect(ops).toEqual(['enter parent']);
ops = [];
simulateMouseMove(firstTarget, secondTarget);
expect(ops).toEqual([
// Parent did not invoke leave because we're still inside the portal.
'enter portal',
]);
ops = [];
simulateMouseMove(secondTarget, thirdTarget);
expect(ops).toEqual([
'leave portal',
'leave parent', // Only when we leave the portal does onMouseLeave fire.
]);
});
it('should throw on bad createPortal argument', () => {
ReactDOM.createPortal(<div>portal</div>, document.createElement('div'));
/*
expect(() => {
ReactDOM.createPortal(<div>portal</div>, null);
}).toThrow('Target container is not a DOM element.');
expect(() => {
ReactDOM.createPortal(<div>portal</div>, document.createTextNode('hi'));
}).toThrow('Target container is not a DOM element.');
*/
});
it('should warn for non-functional event listeners', () => {
class Example extends React.Component {
render() {
return <div onClick="woops" />;
}
}
expect(() => ReactDOM.render(<Example />, container)).toWarnDev(
'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
' in div (at **)\n' +
' in Example (at **)'
);
});
it('should warn with a special message for `false` event listeners', () => {
class Example extends React.Component {
render() {
return <div onClick={false} />;
}
}
expect(() => ReactDOM.render(<Example />, container)).toWarnDev(
'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
'If you used to conditionally omit it with onClick={condition && value}, ' +
'pass onClick={condition ? value : undefined} instead.\n',
' in div (at **)\n' + ' in Example (at **)'
);
});
it('should not update event handlers until commit', () => {
let ops = [];
const handlerA = () => ops.push('A');
const handlerB = () => ops.push('B');
class Example extends React.Component {
state = { flip: false, count: 0 };
flip() {
this.setState({ flip: true, count: this.state.count + 1 });
}
tick() {
this.setState({ count: this.state.count + 1 });
}
render() {
const useB = !this.props.forceA && this.state.flip;
return <div onClick={useB ? handlerB : handlerA} />;
}
}
class Click extends React.Component {
constructor() {
super();
click(node);
}
render() {
return null;
}
}
let inst;
ReactDOM.render([<Example key="a" ref={n => (inst = n)} />], container);
const node = container.firstChild;
expect(node.tagName).toEqual('DIV');
function click(target) {
const fakeNativeEvent = {};
ReactTestUtils.simulateNativeEventOnNode('topClick', target, fakeNativeEvent);
}
click(node);
expect(ops).toEqual(['A']);
ops = [];
// Render with the other event handler.
inst.flip();
click(node);
expect(ops).toEqual(['B']);
ops = [];
// Rerender without changing any props.
inst.tick();
click(node);
expect(ops).toEqual(['B']);
ops = [];
// Render a flip back to the A handler. The second component invokes the
// click handler during render to simulate a click during an aborted
// render. I use this hack because at current time we don't have a way to
// test aborted ReactDOM renders.
ReactDOM.render([<Example key="a" forceA={true} />, <Click key="b" />], container);
// Because the new click handler has not yet committed, we should still
// invoke B.
expect(ops).toEqual(['B']);
ops = [];
// Any click that happens after commit, should invoke A.
click(node);
expect(ops).toEqual(['A']);
});
it('should not crash encountering low-priority tree', () => {
ReactDOM.render(
<div hidden={true}>
<div />
</div>,
container
);
});
it('should not warn when rendering into an empty container', () => {
ReactDOM.render(<div>foo</div>, container);
expect(container.innerHTML).toBe('<div>foo</div>');
ReactDOM.render(null, container);
expect(container.innerHTML).toBe('');
ReactDOM.render(<div>bar</div>, container);
expect(container.innerHTML).toBe('<div>bar</div>');
});
it('should warn when doing an update to a container manually updated outside of React', () => {
// when not messing with the DOM outside of React
ReactDOM.render(<div>foo</div>, container);
ReactDOM.render(<div>bar</div>, container);
expect(container.innerHTML).toBe('<div>bar</div>');
// then we mess with the DOM before an update
container.innerHTML = '<div>MEOW.</div>';
// expect(() => ReactDOM.render(<div>baz</div>, container)).toWarnDev(
// 'render(...): ' +
// 'It looks like the React-rendered content of this container was ' +
// 'removed without using React. This is not supported and will ' +
// 'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
// 'to empty a container.',
// );
});
it('should warn when doing an update to a container manually cleared outside of React', () => {
// when not messing with the DOM outside of React
ReactDOM.render(<div>foo</div>, container);
ReactDOM.render(<div>bar</div>, container);
expect(container.innerHTML).toBe('<div>bar</div>');
// then we mess with the DOM before an update
container.innerHTML = '';
// expect(() => ReactDOM.render(<div>baz</div>, container)).toWarnDev(
// 'render(...): ' +
// 'It looks like the React-rendered content of this container was ' +
// 'removed without using React. This is not supported and will ' +
// 'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
// 'to empty a container.',
// );
});
it('should render a text component with a text DOM node on the same document as the container', () => {
console.log("这不测试有问题,iframe无法跨域")
return
// 1. Create a new document through the use of iframe
// 2. Set up the spy to make asserts when a text component
// is rendered inside the iframe container
const textContent = 'Hello world';
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeDocument = iframe.contentDocument;
iframeDocument.domain = document.domain;
iframeDocument.write('<!DOCTYPE html><html><head></head><body><div></div></body></html>');
iframeDocument.close();
const iframeContainer = iframeDocument.body.firstChild;
let actualDocument;
let textNode;
//anu只使用insertBefore
let oldInsertBefore = iframeContainer.insertBefore;
iframeContainer.insertBefore = function(node, insertPoint) {
actualDocument = node.ownerDocument;
textNode = node;
return oldInsertBefore.call(this, node, insertPoint);
};
// spyOnDevAndProd(iframeContainer, 'appendChild').and.callFake(node => {
// actualDocument = node.ownerDocument;
// textNode = node;
// });
ReactDOM.render(textContent, iframeContainer);
expect(textNode.textContent).toBe(textContent);
// expect(actualDocument).not.toBe(document);
expect(actualDocument).toBe(iframeDocument);
expect(iframeContainer.appendChild).toHaveBeenCalledTimes(1);
});
it('should mount into a document fragment', () => {
const fragment = document.createDocumentFragment();
ReactDOM.render(<div>foo</div>, fragment);
expect(container.innerHTML.trim()).toBe('');
container.appendChild(fragment);
expect(container.innerHTML.trim()).toBe('<div>foo</div>');
});
});