'use strict';
let JSDOM;
let React;
let ReactDOMClient;
let act;
let document;
let Fragment;
let Node;
describe('FragmentRefs', () => {
beforeEach(() => {
jest.resetModules();
JSDOM = require('jsdom');
React = require('react');
Fragment = React.Fragment;
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
const jsdom = new JSDOM.JSDOM('');
document = jsdom.window.document;
Node = jsdom.window.Node;
global.window = jsdom.window;
global.document = global.window.document;
global.navigator = global.window.navigator;
global.Event = global.window.Event;
global.MouseEvent = global.window.MouseEvent;
global.Node = Node;
});
describe('focus methods', () => {
describe('blur()', () => {
it('throws when the nearest host parent is a Document container', async () => {
const fragmentRef = React.createRef();
const root = ReactDOMClient.createRoot(document);
await act(() => {
root.render(
<Fragment ref={fragmentRef}>
<html>
<body>
<a id="child-a" href="/">
A
</a>
</body>
</html>
</Fragment>,
);
});
await act(() => {
document.getElementById('child-a').focus();
});
expect(document.activeElement.id).toEqual('child-a');
await act(() => {
fragmentRef.current.blur();
});
expect(document.activeElement).toEqual(document.body);
});
});
});
describe('events', () => {
describe('dispatchEvent()', () => {
it('fires events when the fragment is a child of a HostSingleton in a document root', async () => {
const fragmentRef = React.createRef();
const bodyRef = React.createRef();
const root = ReactDOMClient.createRoot(document);
await act(() => {
root.render(
<html>
<body ref={bodyRef}>
<Fragment ref={fragmentRef} />
</body>
</html>,
);
});
const fragmentListener = jest.fn();
fragmentRef.current.addEventListener('custom', fragmentListener);
const bodyListener = jest.fn();
bodyRef.current.addEventListener('custom', bodyListener);
fragmentRef.current.dispatchEvent(new Event('custom', {bubbles: true}));
expect(fragmentListener).toHaveBeenCalledTimes(1);
expect(bodyListener).toHaveBeenCalledTimes(1);
});
it('dispatches to its own listeners when the container is a Document', async () => {
const fragmentRef = React.createRef();
const root = ReactDOMClient.createRoot(document);
const logs = [];
await act(() => {
root.render(
<>
<Fragment ref={fragmentRef} />
<html>
<body>
<div id="child" />
</body>
</html>
</>,
);
});
fragmentRef.current.addEventListener('click', () => {
logs.push('fragment');
});
document.addEventListener('click', () => {
logs.push('document');
});
const isCancelable = !fragmentRef.current.dispatchEvent(
new MouseEvent('click', {bubbles: true}),
);
expect(logs).toEqual(['fragment', 'document']);
expect(isCancelable).toBe(false);
});
it('does not propagate through its own children when wrapping documentElement', async () => {
const fragmentRef = React.createRef();
const root = ReactDOMClient.createRoot(document);
const logs = [];
await act(() => {
root.render(
<Fragment ref={fragmentRef}>
<html>
<body>
<div id="child" />
</body>
</html>
</Fragment>,
);
});
fragmentRef.current.addEventListener('click', () => {
logs.push('fragment');
});
document.addEventListener('click', () => {
logs.push('document');
});
fragmentRef.current.dispatchEvent(
new MouseEvent('click', {bubbles: true}),
);
expect(logs).toEqual(['fragment', 'document']);
});
it('dispatches non-bubbling events when the container is a Document', async () => {
const fragmentRef = React.createRef();
const root = ReactDOMClient.createRoot(document);
const logs = [];
await act(() => {
root.render(
<>
<Fragment ref={fragmentRef} />
<html>
<body>
<div id="child" />
</body>
</html>
</>,
);
});
document.addEventListener('click', () => {
logs.push('document');
});
const isCancelable = !fragmentRef.current.dispatchEvent(
new MouseEvent('click', {bubbles: false}),
);
expect(logs).toEqual([]);
expect(isCancelable).toBe(false);
});
});
describe('addEventListener()', () => {
it('attaches listeners to the host children inside singletons', async () => {
const fragmentRef = React.createRef();
const childRef = React.createRef();
const root = ReactDOMClient.createRoot(document);
await act(() => {
root.render(
<Fragment ref={fragmentRef}>
<html>
<body>
<div ref={childRef} id="child" />
</body>
</html>
</Fragment>,
);
});
const currentTargets = [];
fragmentRef.current.addEventListener('click', event => {
currentTargets.push(event.currentTarget);
});
childRef.current.dispatchEvent(new Event('click', {bubbles: true}));
expect(currentTargets).toEqual([document.documentElement]);
});
it('attaches listeners to a singleton mounted into the fragment, but not to its content', async () => {
const fragmentRef = React.createRef();
const childRef = React.createRef();
const root = ReactDOMClient.createRoot(document);
function Test({showShell}) {
return (
<Fragment ref={fragmentRef}>
{showShell && (
<html>
<body>
<div ref={childRef} id="child" />
</body>
</html>
)}
</Fragment>
);
}
await act(() => {
root.render(<Test showShell={false} />);
});
const currentTargets = [];
fragmentRef.current.addEventListener('click', event => {
currentTargets.push(event.currentTarget);
});
await act(() => {
root.render(<Test showShell={true} />);
});
childRef.current.dispatchEvent(new Event('click', {bubbles: true}));
expect(currentTargets).toEqual([document.documentElement]);
});
it('attributes new children inside a singleton to fragments below it, not above it', async () => {
const outerFragmentRef = React.createRef();
const innerFragmentRef = React.createRef();
const lateChildRef = React.createRef();
const root = ReactDOMClient.createRoot(document);
function Test({showLateChild}) {
return (
<Fragment ref={outerFragmentRef}>
<html>
<body>
<Fragment ref={innerFragmentRef}>
<div id="child" />
{showLateChild && <span ref={lateChildRef} id="late" />}
</Fragment>
</body>
</html>
</Fragment>
);
}
await act(() => {
root.render(<Test showLateChild={false} />);
});
const outerCurrentTargets = [];
outerFragmentRef.current.addEventListener('click', event => {
outerCurrentTargets.push(event.currentTarget);
});
const innerCurrentTargets = [];
innerFragmentRef.current.addEventListener('click', event => {
innerCurrentTargets.push(event.currentTarget);
});
await act(() => {
root.render(<Test showLateChild={true} />);
});
lateChildRef.current.dispatchEvent(new Event('click', {bubbles: true}));
expect(innerCurrentTargets).toEqual([lateChildRef.current]);
expect(outerCurrentTargets).toEqual([document.documentElement]);
});
});
});
describe('getClientRects()', () => {
it('measures the host children inside singletons', async () => {
const fragmentRef = React.createRef();
const childRef = React.createRef();
const root = ReactDOMClient.createRoot(document);
await act(() => {
root.render(
<Fragment ref={fragmentRef}>
<html>
<body>
<div ref={childRef} id="child" />
</body>
</html>
</Fragment>,
);
});
childRef.current.getClientRects = jest.fn(() => ['child-rect']);
document.documentElement.getClientRects = jest.fn(() => ['html-rect']);
expect(fragmentRef.current.getClientRects()).toEqual(['html-rect']);
});
});
describe('compareDocumentPosition', () => {
function expectPosition(position, spec) {
const positionResult = {
following: (position & Node.DOCUMENT_POSITION_FOLLOWING) !== 0,
preceding: (position & Node.DOCUMENT_POSITION_PRECEDING) !== 0,
contains: (position & Node.DOCUMENT_POSITION_CONTAINS) !== 0,
containedBy: (position & Node.DOCUMENT_POSITION_CONTAINED_BY) !== 0,
disconnected: (position & Node.DOCUMENT_POSITION_DISCONNECTED) !== 0,
implementationSpecific:
(position & Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) !== 0,
};
expect(positionResult).toEqual(spec);
}
it('treats documentElement as containing the fragment', async () => {
const fragmentRef = React.createRef();
const container = document.createElement('div');
document.body.appendChild(container);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<Fragment ref={fragmentRef}>
<div id="child" />
</Fragment>,
);
});
expectPosition(
fragmentRef.current.compareDocumentPosition(document.documentElement),
{
preceding: true,
following: false,
contains: true,
containedBy: false,
disconnected: false,
implementationSpecific: false,
},
);
});
});
});