import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/src/ReactFiberTreeReflection';
import {get as getInstance} from 'shared/ReactInstanceMap';
import {
ClassComponent,
FunctionComponent,
HostComponent,
HostHoistable,
HostSingleton,
HostText,
} from 'react-reconciler/src/ReactWorkTags';
import {SyntheticEvent} from 'react-dom-bindings/src/events/SyntheticEvent';
import {ELEMENT_NODE} from 'react-dom-bindings/src/client/HTMLNodeType';
import {
rethrowCaughtError,
invokeGuardedCallbackAndCatchFirstError,
} from 'shared/ReactErrorUtils';
import {enableFloat, enableHostSingletons} from 'shared/ReactFeatureFlags';
import assign from 'shared/assign';
import isArray from 'shared/isArray';
const SecretInternals =
ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
const EventInternals = SecretInternals.Events;
const getInstanceFromNode = EventInternals[0];
const getNodeFromInstance = EventInternals[1];
const getFiberCurrentPropsFromNode = EventInternals[2];
const enqueueStateRestore = EventInternals[3];
const restoreStateIfNeeded = EventInternals[4];
const act = React.unstable_act;
function Event(suffix) {}
let hasWarnedAboutDeprecatedMockComponent = false;
function findAllInRenderedFiberTreeInternal(fiber, test) {
if (!fiber) {
return [];
}
const currentParent = findCurrentFiberUsingSlowPath(fiber);
if (!currentParent) {
return [];
}
let node = currentParent;
const ret = [];
while (true) {
if (
node.tag === HostComponent ||
node.tag === HostText ||
node.tag === ClassComponent ||
node.tag === FunctionComponent ||
(enableFloat ? node.tag === HostHoistable : false) ||
(enableHostSingletons ? node.tag === HostSingleton : false)
) {
const publicInst = node.stateNode;
if (test(publicInst)) {
ret.push(publicInst);
}
}
if (node.child) {
node.child.return = node;
node = node.child;
continue;
}
if (node === currentParent) {
return ret;
}
while (!node.sibling) {
if (!node.return || node.return === currentParent) {
return ret;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function validateClassInstance(inst, methodName) {
if (!inst) {
return;
}
if (getInstance(inst)) {
return;
}
let received;
const stringified = String(inst);
if (isArray(inst)) {
received = 'an array';
} else if (inst && inst.nodeType === ELEMENT_NODE && inst.tagName) {
received = 'a DOM node';
} else if (stringified === '[object Object]') {
received = 'object with keys {' + Object.keys(inst).join(', ') + '}';
} else {
received = stringified;
}
throw new Error(
`${methodName}(...): the first argument must be a React class instance. ` +
`Instead received: ${received}.`,
);
}
function renderIntoDocument(element) {
const div = document.createElement('div');
return ReactDOM.render(element, div);
}
function isElement(element) {
return React.isValidElement(element);
}
function isElementOfType(inst, convenienceConstructor) {
return React.isValidElement(inst) && inst.type === convenienceConstructor;
}
function isDOMComponent(inst) {
return !!(inst && inst.nodeType === ELEMENT_NODE && inst.tagName);
}
function isDOMComponentElement(inst) {
return !!(inst && React.isValidElement(inst) && !!inst.tagName);
}
function isCompositeComponent(inst) {
if (isDOMComponent(inst)) {
return false;
}
return (
inst != null &&
typeof inst.render === 'function' &&
typeof inst.setState === 'function'
);
}
function isCompositeComponentWithType(inst, type) {
if (!isCompositeComponent(inst)) {
return false;
}
const internalInstance = getInstance(inst);
const constructor = internalInstance.type;
return constructor === type;
}
function findAllInRenderedTree(inst, test) {
validateClassInstance(inst, 'findAllInRenderedTree');
if (!inst) {
return [];
}
const internalInstance = getInstance(inst);
return findAllInRenderedFiberTreeInternal(internalInstance, test);
}
function scryRenderedDOMComponentsWithClass(root, classNames) {
validateClassInstance(root, 'scryRenderedDOMComponentsWithClass');
return findAllInRenderedTree(root, function (inst) {
if (isDOMComponent(inst)) {
let className = inst.className;
if (typeof className !== 'string') {
className = inst.getAttribute('class') || '';
}
const classList = className.split(/\s+/);
if (!isArray(classNames)) {
if (classNames === undefined) {
throw new Error(
'TestUtils.scryRenderedDOMComponentsWithClass expects a ' +
'className as a second argument.',
);
}
classNames = classNames.split(/\s+/);
}
return classNames.every(function (name) {
return classList.indexOf(name) !== -1;
});
}
return false;
});
}
function findRenderedDOMComponentWithClass(root, className) {
validateClassInstance(root, 'findRenderedDOMComponentWithClass');
const all = scryRenderedDOMComponentsWithClass(root, className);
if (all.length !== 1) {
throw new Error(
'Did not find exactly one match (found: ' +
all.length +
') ' +
'for class:' +
className,
);
}
return all[0];
}
function scryRenderedDOMComponentsWithTag(root, tagName) {
validateClassInstance(root, 'scryRenderedDOMComponentsWithTag');
return findAllInRenderedTree(root, function (inst) {
return (
isDOMComponent(inst) &&
inst.tagName.toUpperCase() === tagName.toUpperCase()
);
});
}
function findRenderedDOMComponentWithTag(root, tagName) {
validateClassInstance(root, 'findRenderedDOMComponentWithTag');
const all = scryRenderedDOMComponentsWithTag(root, tagName);
if (all.length !== 1) {
throw new Error(
'Did not find exactly one match (found: ' +
all.length +
') ' +
'for tag:' +
tagName,
);
}
return all[0];
}
function scryRenderedComponentsWithType(root, componentType) {
validateClassInstance(root, 'scryRenderedComponentsWithType');
return findAllInRenderedTree(root, function (inst) {
return isCompositeComponentWithType(inst, componentType);
});
}
function findRenderedComponentWithType(root, componentType) {
validateClassInstance(root, 'findRenderedComponentWithType');
const all = scryRenderedComponentsWithType(root, componentType);
if (all.length !== 1) {
throw new Error(
'Did not find exactly one match (found: ' +
all.length +
') ' +
'for componentType:' +
componentType,
);
}
return all[0];
}
function mockComponent(module, mockTagName) {
if (__DEV__) {
if (!hasWarnedAboutDeprecatedMockComponent) {
hasWarnedAboutDeprecatedMockComponent = true;
console.warn(
'ReactTestUtils.mockComponent() is deprecated. ' +
'Use shallow rendering or jest.mock() instead.\n\n' +
'See https://reactjs.org/link/test-utils-mock-component for more information.',
);
}
}
mockTagName = mockTagName || module.mockTagName || 'div';
module.prototype.render.mockImplementation(function () {
return React.createElement(mockTagName, null, this.props.children);
});
return this;
}
function nativeTouchData(x, y) {
return {
touches: [{pageX: x, pageY: y}],
};
}
function executeDispatch(event, listener, inst) {
const type = event.type || 'unknown-event';
event.currentTarget = getNodeFromInstance(inst);
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
event.currentTarget = null;
}
function executeDispatchesInOrder(event) {
const dispatchListeners = event._dispatchListeners;
const dispatchInstances = event._dispatchInstances;
if (isArray(dispatchListeners)) {
for (let i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
}
} else if (dispatchListeners) {
executeDispatch(event, dispatchListeners, dispatchInstances);
}
event._dispatchListeners = null;
event._dispatchInstances = null;
}
function executeDispatchesAndRelease(event ) {
if (event) {
executeDispatchesInOrder(event);
if (!event.isPersistent()) {
event.constructor.release(event);
}
}
}
function isInteractive(tag) {
return (
tag === 'button' ||
tag === 'input' ||
tag === 'select' ||
tag === 'textarea'
);
}
function getParent(inst) {
do {
inst = inst.return;
} while (
inst &&
inst.tag !== HostComponent &&
(!enableHostSingletons ? true : inst.tag !== HostSingleton)
);
if (inst) {
return inst;
}
return null;
}
export function traverseTwoPhase(inst, fn, arg) {
const path = [];
while (inst) {
path.push(inst);
inst = getParent(inst);
}
let i;
for (i = path.length; i-- > 0; ) {
fn(path[i], 'captured', arg);
}
for (i = 0; i < path.length; i++) {
fn(path[i], 'bubbled', arg);
}
}
function shouldPreventMouseEvent(name, type, props) {
switch (name) {
case 'onClick':
case 'onClickCapture':
case 'onDoubleClick':
case 'onDoubleClickCapture':
case 'onMouseDown':
case 'onMouseDownCapture':
case 'onMouseMove':
case 'onMouseMoveCapture':
case 'onMouseUp':
case 'onMouseUpCapture':
case 'onMouseEnter':
return !!(props.disabled && isInteractive(type));
default:
return false;
}
}
function getListener(inst , registrationName: string) {
const stateNode = inst.stateNode;
if (!stateNode) {
return null;
}
const props = getFiberCurrentPropsFromNode(stateNode);
if (!props) {
return null;
}
const listener = props[registrationName];
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
return null;
}
if (listener && typeof listener !== 'function') {
throw new Error(
`Expected \`${registrationName}\` listener to be a function, instead got a value of \`${typeof listener}\` type.`,
);
}
return listener;
}
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
let registrationName = event._reactName;
if (propagationPhase === 'captured') {
registrationName += 'Capture';
}
return getListener(inst, registrationName);
}
function accumulateDispatches(inst, ignoredDirection, event) {
if (inst && event && event._reactName) {
const registrationName = event._reactName;
const listener = getListener(inst, registrationName);
if (listener) {
if (event._dispatchListeners == null) {
event._dispatchListeners = [];
}
if (event._dispatchInstances == null) {
event._dispatchInstances = [];
}
event._dispatchListeners.push(listener);
event._dispatchInstances.push(inst);
}
}
}
function accumulateDirectionalDispatches(inst, phase, event) {
if (__DEV__) {
if (!inst) {
console.error('Dispatching inst must not be null');
}
}
const listener = listenerAtPhase(inst, event, phase);
if (listener) {
if (event._dispatchListeners == null) {
event._dispatchListeners = [];
}
if (event._dispatchInstances == null) {
event._dispatchInstances = [];
}
event._dispatchListeners.push(listener);
event._dispatchInstances.push(inst);
}
}
function accumulateDirectDispatchesSingle(event) {
if (event && event._reactName) {
accumulateDispatches(event._targetInst, null, event);
}
}
function accumulateTwoPhaseDispatchesSingle(event) {
if (event && event._reactName) {
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
}
}
const Simulate = {};
const directDispatchEventTypes = new Set([
'mouseEnter',
'mouseLeave',
'pointerEnter',
'pointerLeave',
]);
function makeSimulator(eventType) {
return function (domNode, eventData) {
if (React.isValidElement(domNode)) {
throw new Error(
'TestUtils.Simulate expected a DOM node as the first argument but received ' +
'a React element. Pass the DOM node you wish to simulate the event on instead. ' +
'Note that TestUtils.Simulate will not work if you are using shallow rendering.',
);
}
if (isCompositeComponent(domNode)) {
throw new Error(
'TestUtils.Simulate expected a DOM node as the first argument but received ' +
'a component instance. Pass the DOM node you wish to simulate the event on instead.',
);
}
const reactName = 'on' + eventType[0].toUpperCase() + eventType.slice(1);
const fakeNativeEvent = new Event();
fakeNativeEvent.target = domNode;
fakeNativeEvent.type = eventType.toLowerCase();
const targetInst = getInstanceFromNode(domNode);
const event = new SyntheticEvent(
reactName,
fakeNativeEvent.type,
targetInst,
fakeNativeEvent,
domNode,
);
event.persist();
assign(event, eventData);
if (directDispatchEventTypes.has(eventType)) {
accumulateDirectDispatchesSingle(event);
} else {
accumulateTwoPhaseDispatchesSingle(event);
}
ReactDOM.unstable_batchedUpdates(function () {
enqueueStateRestore(domNode);
executeDispatchesAndRelease(event);
rethrowCaughtError();
});
restoreStateIfNeeded();
};
}
const simulatedEventTypes = [
'blur',
'cancel',
'click',
'close',
'contextMenu',
'copy',
'cut',
'auxClick',
'doubleClick',
'dragEnd',
'dragStart',
'drop',
'focus',
'input',
'invalid',
'keyDown',
'keyPress',
'keyUp',
'mouseDown',
'mouseUp',
'paste',
'pause',
'play',
'pointerCancel',
'pointerDown',
'pointerUp',
'rateChange',
'reset',
'resize',
'seeked',
'submit',
'touchCancel',
'touchEnd',
'touchStart',
'volumeChange',
'drag',
'dragEnter',
'dragExit',
'dragLeave',
'dragOver',
'mouseMove',
'mouseOut',
'mouseOver',
'pointerMove',
'pointerOut',
'pointerOver',
'scroll',
'toggle',
'touchMove',
'wheel',
'abort',
'animationEnd',
'animationIteration',
'animationStart',
'canPlay',
'canPlayThrough',
'durationChange',
'emptied',
'encrypted',
'ended',
'error',
'gotPointerCapture',
'load',
'loadedData',
'loadedMetadata',
'loadStart',
'lostPointerCapture',
'playing',
'progress',
'seeking',
'stalled',
'suspend',
'timeUpdate',
'transitionEnd',
'waiting',
'mouseEnter',
'mouseLeave',
'pointerEnter',
'pointerLeave',
'change',
'select',
'beforeInput',
'compositionEnd',
'compositionStart',
'compositionUpdate',
];
function buildSimulators() {
simulatedEventTypes.forEach(eventType => {
Simulate[eventType] = makeSimulator(eventType);
});
}
buildSimulators();
export {
renderIntoDocument,
isElement,
isElementOfType,
isDOMComponent,
isDOMComponentElement,
isCompositeComponent,
isCompositeComponentWithType,
findAllInRenderedTree,
scryRenderedDOMComponentsWithClass,
findRenderedDOMComponentWithClass,
scryRenderedDOMComponentsWithTag,
findRenderedDOMComponentWithTag,
scryRenderedComponentsWithType,
findRenderedComponentWithType,
mockComponent,
nativeTouchData,
Simulate,
act,
};