@chalbert/enzyme-adapter-react-18
Version:
JavaScript Testing utilities for React
966 lines (880 loc) • 28.6 kB
JavaScript
import React from 'react';
import { flushSync, findDOMNode } from 'react-dom';
import { createRoot, hydrateRoot } from 'react-dom/client';
import ReactDOMServer from 'react-dom/server';
import ShallowRenderer from 'react-test-renderer/shallow';
import TestUtils from 'react-dom/test-utils';
import checkPropTypes from 'prop-types/checkPropTypes';
import has from 'has';
import {
ConcurrentMode,
ContextConsumer,
ContextProvider,
Element,
ForwardRef,
Fragment,
isContextConsumer,
isContextProvider,
isElement,
isForwardRef,
isPortal,
isSuspense,
isValidElementType,
Lazy,
Memo,
Portal,
Profiler,
StrictMode,
Suspense,
} from 'react-is';
import { EnzymeAdapter, ReactWrapper } from 'enzyme';
import { typeOfNode } from 'enzyme/build/Utils';
import shallowEqual from 'enzyme-shallow-equal';
import {
displayNameOfNode,
elementToTree as utilElementToTree,
nodeTypeFromType as utilNodeTypeFromType,
mapNativeEventNames,
propFromEvent,
assertDomAvailable,
withSetStateAllowed,
createRenderWrapper,
createMountWrapper,
propsWithKeysAndRef,
ensureKeyOrUndefined,
simulateError,
wrap,
getMaskedContext,
getComponentStack,
RootFinder,
getNodeFromRootFinder,
wrapWithWrappingComponent,
getWrappingComponentMountRenderer,
compareNodeTypeOf,
spyMethod,
getPublicRootInstance,
TAG_CODES,
} from '@chalbert/enzyme-adapter-utils';
import findCurrentFiberUsingSlowPath from './findCurrentFiberUsingSlowPath';
// Lazily populated if DOM is available.
function nodeAndSiblingsArray(nodeWithSibling) {
const array = [];
let node = nodeWithSibling;
while (node != null) {
array.push(node);
node = node.sibling;
}
return array;
}
function flatten(arr) {
const result = [];
const stack = [{ i: 0, array: arr }];
while (stack.length) {
const n = stack.pop();
while (n.i < n.array.length) {
const el = n.array[n.i];
n.i += 1;
if (Array.isArray(el)) {
stack.push(n);
stack.push({ i: 0, array: el });
break;
}
result.push(el);
}
}
return result;
}
function nodeTypeFromType(type) {
if (type === Portal) {
return 'portal';
}
return utilNodeTypeFromType(type);
}
function isMemo(type) {
return compareNodeTypeOf(type, Memo);
}
function isLazy(type) {
return compareNodeTypeOf(type, Lazy);
}
function unmemoType(type) {
return isMemo(type) ? type.type : type;
}
function checkIsSuspenseAndCloneElement(el, { suspenseFallback }) {
if (!isSuspense(el)) {
return el;
}
let { children } = el.props;
if (suspenseFallback) {
const { fallback } = el.props;
children = replaceLazyWithFallback(children, fallback);
}
const FakeSuspenseWrapper = (props) =>
React.createElement(el.type, { ...el.props, ...props }, children);
return React.createElement(FakeSuspenseWrapper, null, children);
}
function elementToTree(el) {
if (!isPortal(el)) {
return utilElementToTree(el, elementToTree);
}
const { children, containerInfo } = el;
const props = { children, containerInfo };
return {
nodeType: 'portal',
type: Portal,
props,
key: ensureKeyOrUndefined(el.key),
ref: el.ref || null,
instance: null,
rendered: elementToTree(el.children),
};
}
function toTree(vnode) {
if (vnode == null) {
return null;
}
// TODO(lmr): I'm not really sure I understand whether or not this is what
// i should be doing, or if this is a hack for something i'm doing wrong
// somewhere else. Should talk to sebastian about this perhaps
const node = findCurrentFiberUsingSlowPath(vnode);
switch (node.tag) {
case TAG_CODES.HostRoot:
return childrenToTree(node.child);
case TAG_CODES.HostPortal: {
const {
stateNode: { containerInfo },
memoizedProps: children,
} = node;
const props = { containerInfo, children };
return {
nodeType: 'portal',
type: Portal,
props,
key: ensureKeyOrUndefined(node.key),
ref: node.ref,
instance: null,
rendered: childrenToTree(node.child),
};
}
case TAG_CODES.IndeterminateComponent:
return {
nodeType: 'indeterminate',
type: node.type,
props: { ...node.memoizedProps },
key: ensureKeyOrUndefined(node.key),
ref: node.ref,
instance: node.stateNode,
rendered: childrenToTree(node.child),
};
case TAG_CODES.IncompleteClassComponent:
case TAG_CODES.ClassComponent:
return {
nodeType: 'class',
type: node.type,
props: { ...node.memoizedProps },
key: ensureKeyOrUndefined(node.key),
ref: node.ref,
instance: node.stateNode,
rendered: childrenToTree(node.child),
};
case TAG_CODES.FunctionComponent:
return {
nodeType: 'function',
type: node.type,
props: { ...node.memoizedProps },
key: ensureKeyOrUndefined(node.key),
ref: node.ref,
instance: null,
rendered: childrenToTree(node.child),
};
case TAG_CODES.MemoClass:
return {
nodeType: 'class',
type: node.elementType.type,
props: { ...node.memoizedProps },
key: ensureKeyOrUndefined(node.key),
ref: node.ref,
instance: node.stateNode,
rendered: childrenToTree(node.child.child),
};
case TAG_CODES.MemoSFC: {
let renderedNodes = flatten(nodeAndSiblingsArray(node.child).map(toTree));
if (renderedNodes.length === 0) {
renderedNodes = [node.memoizedProps.children];
}
return {
nodeType: 'function',
type: node.elementType,
props: { ...node.memoizedProps },
key: ensureKeyOrUndefined(node.key),
ref: node.ref,
instance: null,
rendered: renderedNodes,
};
}
case TAG_CODES.HostComponent: {
let renderedNodes = flatten(nodeAndSiblingsArray(node.child).map(toTree));
if (renderedNodes.length === 0) {
renderedNodes = [node.memoizedProps.children];
}
return {
nodeType: 'host',
type: node.type,
props: { ...node.memoizedProps },
key: ensureKeyOrUndefined(node.key),
ref: node.ref,
instance: node.stateNode,
rendered: renderedNodes,
};
}
case TAG_CODES.HostText:
return node.memoizedProps;
case TAG_CODES.DehydratedFragment:
case TAG_CODES.Fragment:
case TAG_CODES.Mode:
case TAG_CODES.ContextProvider:
case TAG_CODES.ContextConsumer:
return childrenToTree(node.child);
case TAG_CODES.Profiler:
case TAG_CODES.MemoComponent:
case TAG_CODES.SimpleMemoComponent:
case TAG_CODES.ForwardRef: {
return {
nodeType: 'function',
type: node.type,
props: { ...node.pendingProps },
key: ensureKeyOrUndefined(node.key),
ref: node.ref,
instance: null,
rendered: childrenToTree(node.child),
};
}
case TAG_CODES.SuspenseListComponent:
case TAG_CODES.SuspenseComponent: {
const children = flatten(nodeAndSiblingsArray(node.child.child).map(toTree));
return {
nodeType: 'function',
type: Suspense,
props: { ...node.memoizedProps },
key: ensureKeyOrUndefined(node.key),
ref: node.ref,
instance: null,
rendered: children.length ? children : childrenToTree(node.child),
};
}
case TAG_CODES.LazyComponent:
return childrenToTree(node.child);
case TAG_CODES.ScopeComponent:
case TAG_CODES.OffscreenComponent:
case TAG_CODES.LegacyHiddenComponent:
case TAG_CODES.CacheComponent:
case TAG_CODES.TracingMarkerComponent:
return toTree(node.child);
default:
throw new Error(`Enzyme Internal Error: unknown node with tag ${node.tag}`);
}
}
function childrenToTree(node) {
if (!node) {
return null;
}
const children = nodeAndSiblingsArray(node);
if (children.length === 0) {
return null;
}
if (children.length === 1) {
return toTree(children[0]);
}
return flatten(children.map(toTree));
}
function nodeToHostNode(_node) {
// NOTE(lmr): node could be a function component
// which wont have an instance prop, but we can get the
// host node associated with its return value at that point.
// Although this breaks down if the return value is an array,
// as is possible with React 16.
let node = _node;
while (node && !Array.isArray(node) && node.instance === null) {
node = node.rendered;
}
// if the SFC returned null effectively, there is no host node.
if (!node) {
return null;
}
const mapper = (item) => {
if (item && item.instance) return findDOMNode(item.instance);
return null;
};
if (Array.isArray(node)) {
return node.map(mapper);
}
if (Array.isArray(node.rendered) && node.nodeType === 'class') {
return node.rendered.map(mapper);
}
return mapper(node);
}
function replaceLazyWithFallback(node, fallback) {
if (!node) {
return null;
}
if (Array.isArray(node)) {
return node.map((el) => replaceLazyWithFallback(el, fallback));
}
if (isLazy(node.type)) {
// console.log('-----------------replaceLazyWithFallback');
return fallback;
}
return {
...node,
props: {
...node.props,
children: replaceLazyWithFallback(node.props.children, fallback),
},
};
}
function getEmptyStateValue() {
// this handles a bug in React 16.0 - 16.2
// see https://github.com/facebook/react/commit/39be83565c65f9c522150e52375167568a2a1459
// also see https://github.com/facebook/react/pull/11965
class EmptyState extends React.Component {
render() {
return null;
}
}
const testRenderer = new ShallowRenderer();
testRenderer.render(React.createElement(EmptyState));
return testRenderer._instance.state;
}
function wrapAct(fn) {
let returnVal;
TestUtils.act(() => {
returnVal = fn();
});
return returnVal;
}
function getProviderDefaultValue(Provider) {
// React stores references to the Provider's defaultValue differently across versions.
if ('_defaultValue' in Provider._context) {
return Provider._context._defaultValue;
}
if ('_currentValue' in Provider._context) {
return Provider._context._currentValue;
}
throw new Error('Enzyme Internal Error: can’t figure out how to get Provider’s default value');
}
function makeFakeElement(type) {
return { $$typeof: Element, type };
}
function isStateful(Component) {
return (
Component.prototype &&
(Component.prototype.isReactComponent || Array.isArray(Component.__reactAutoBindPairs)) // fallback for createClass components
);
}
class ReactEighteenAdapter extends EnzymeAdapter {
constructor() {
super();
// Monkey patching so we don't have to wrap in 'act' each time
const { setProps, setState } = ReactWrapper.prototype;
ReactWrapper.prototype.setProps = function (...args) {
return wrapAct(() => setProps.apply(this, args));
};
ReactWrapper.prototype.setState = function (...args) {
return wrapAct(() => setState.apply(this, args));
};
const { lifecycles } = this.options;
this.options = {
...this.options,
enableComponentDidUpdateOnSetState: true, // TODO: remove, semver-major
legacyContextMode: 'parent',
lifecycles: {
...lifecycles,
componentDidUpdate: {
onSetState: true,
},
getDerivedStateFromProps: {
hasShouldComponentUpdateBug: false,
},
getSnapshotBeforeUpdate: true,
setState: {
skipsComponentDidUpdateOnNullish: true,
},
getChildContext: {
calledByRenderer: false,
},
getDerivedStateFromError: true,
},
};
}
createMountRenderer(options) {
assertDomAvailable('mount');
if (has(options, 'suspenseFallback')) {
throw new TypeError('`suspenseFallback` is not supported by the `mount` renderer');
}
const { attachTo, hydrateIn, wrappingComponentProps } = options;
const domNode = hydrateIn || attachTo || global.document.createElement('div');
let instance = null;
const adapter = this;
let root;
return {
render(el, context, callback) {
return wrapAct(() => {
if (instance === null) {
const { type, props, ref } = el;
const wrapperProps = {
Component: type,
props,
wrappingComponentProps,
context,
...(ref && { refProp: ref }),
};
let wrappedEl;
const ReactWrapperComponent = createMountWrapper(el, { ...options, adapter });
wrappedEl = React.createElement(ReactWrapperComponent, wrapperProps);
root = hydrateIn ? hydrateRoot(domNode, wrappedEl) : createRoot(domNode);
flushSync(() => {
root.render(wrappedEl);
});
instance = getPublicRootInstance(root);
if (typeof callback === 'function') {
callback();
}
} else {
instance.setChildProps(el.props, context, callback);
}
});
},
unmount() {
wrapAct(() => {
root?.unmount();
});
instance = null;
},
getNode() {
if (!instance) {
return null;
}
return getNodeFromRootFinder(
adapter.isCustomComponent,
toTree(instance._reactInternals),
options,
);
},
simulateError(nodeHierarchy, rootNode, error) {
const isErrorBoundary = ({ instance: elInstance, type }) => {
if (type && type.getDerivedStateFromError) {
return true;
}
return elInstance && elInstance.componentDidCatch;
};
const { instance: catchingInstance, type: catchingType } =
nodeHierarchy.find(isErrorBoundary) || {};
TestUtils.act(() => {
simulateError(
error,
catchingInstance,
rootNode,
nodeHierarchy,
nodeTypeFromType,
adapter.displayNameOfNode,
catchingType,
);
});
},
simulateEvent(node, event, mock) {
const mappedEvent = mapNativeEventNames(event);
const eventFn = TestUtils.Simulate[mappedEvent];
if (!eventFn) {
throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
}
wrapAct(() => {
eventFn(adapter.nodeToHostNode(node), mock);
});
},
batchedUpdates(fn) {
return fn();
},
getWrappingComponentRenderer() {
return {
...this,
...getWrappingComponentMountRenderer({
toTree: (inst) => toTree(inst._reactInternals),
getMountWrapperInstance: () => instance,
}),
};
},
wrapInvoke: wrapAct,
};
}
createShallowRenderer(options = {}) {
const adapter = this;
const renderer = new ShallowRenderer();
const { suspenseFallback } = options;
if (typeof suspenseFallback !== 'undefined' && typeof suspenseFallback !== 'boolean') {
throw TypeError('`options.suspenseFallback` should be boolean or undefined');
}
let isDOM = false;
let cachedNode = null;
let lastComponent = null;
let wrappedComponent = null;
const sentinel = {};
// wrap memo components with a PureComponent, or a class component with sCU
const wrapPureComponent = (Component, compare) => {
if (lastComponent !== Component) {
if (isStateful(Component)) {
wrappedComponent = class extends Component {};
if (compare) {
wrappedComponent.prototype.shouldComponentUpdate = (nextProps) =>
!compare(this.props, nextProps);
} else {
wrappedComponent.prototype.isPureReactComponent = true;
}
} else {
let memoized = sentinel;
let prevProps;
wrappedComponent = function wrappedComponentFn(props, ...args) {
const shouldUpdate =
memoized === sentinel ||
(compare ? !compare(prevProps, props) : !shallowEqual(prevProps, props));
if (shouldUpdate) {
memoized = Component({ ...Component.defaultProps, ...props }, ...args);
prevProps = props;
}
return memoized;
};
}
Object.assign(wrappedComponent, Component, {
displayName: adapter.displayNameOfNode({ type: Component }),
});
lastComponent = Component;
}
return wrappedComponent;
};
// Wrap functional components on versions prior to 16.5,
// to avoid inadvertently pass a `this` instance to it.
const wrapFunctionalComponent = (Component) => {
if (has(Component, 'defaultProps')) {
if (lastComponent !== Component) {
wrappedComponent = Object.assign(
(props, ...args) => Component({ ...Component.defaultProps, ...props }, ...args),
Component,
{ displayName: adapter.displayNameOfNode({ type: Component }) },
);
lastComponent = Component;
}
return wrappedComponent;
}
return Component;
};
const renderElement = (elConfig, ...rest) => {
// console.log('**************** renderElement');
const renderedEl = renderer.render(elConfig, ...rest);
const typeIsExisted = !!(renderedEl && renderedEl.type);
if (typeIsExisted) {
const clonedEl = checkIsSuspenseAndCloneElement(renderedEl, { suspenseFallback });
const elementIsChanged = clonedEl.type !== renderedEl.type;
if (elementIsChanged) {
return renderer.render({ ...elConfig, type: clonedEl.type }, ...rest);
}
}
return renderedEl;
};
return {
render(el, unmaskedContext, { providerValues = new Map() } = {}) {
cachedNode = el;
if (typeof el.type === 'string') {
isDOM = true;
} else if (isContextProvider(el)) {
providerValues.set(el.type, el.props.value);
const MockProvider = Object.assign((props) => props.children, el.type);
return withSetStateAllowed(() => renderElement({ ...el, type: MockProvider }));
} else if (isContextConsumer(el)) {
const Provider = adapter.getProviderFromConsumer(el.type);
const value = providerValues.has(Provider)
? providerValues.get(Provider)
: getProviderDefaultValue(Provider);
const MockConsumer = Object.assign((props) => props.children(value), el.type);
return withSetStateAllowed(() => renderElement({ ...el, type: MockConsumer }));
} else {
isDOM = false;
let renderedEl = el;
if (isLazy(renderedEl)) {
throw TypeError('`React.lazy` is not supported by shallow rendering.');
}
renderedEl = checkIsSuspenseAndCloneElement(renderedEl, { suspenseFallback });
const { type: Component } = renderedEl;
const context = getMaskedContext(Component.contextTypes, unmaskedContext);
if (isMemo(el.type)) {
const { type: InnerComp, compare } = el.type;
return withSetStateAllowed(() =>
renderElement({ ...el, type: wrapPureComponent(InnerComp, compare) }, context),
);
}
const isComponentStateful = isStateful(Component);
if (!isComponentStateful && typeof Component === 'function') {
return withSetStateAllowed(() =>
renderElement({ ...renderedEl, type: wrapFunctionalComponent(Component) }, context),
);
}
if (isComponentStateful) {
if (
renderer._instance &&
el.props === renderer._instance.props &&
!shallowEqual(context, renderer._instance.context)
) {
const { restore } = spyMethod(
renderer,
'_updateClassComponent',
(originalMethod) =>
function _updateClassComponent(...args) {
const { props } = renderer._instance;
const clonedProps = { ...props };
renderer._instance.props = clonedProps;
const result = originalMethod.apply(renderer, args);
renderer._instance.props = props;
restore();
return result;
},
);
}
// fix react bug; see implementation of `getEmptyStateValue`
const emptyStateValue = getEmptyStateValue();
if (emptyStateValue) {
Object.defineProperty(Component.prototype, 'state', {
configurable: true,
enumerable: true,
get() {
return null;
},
set(value) {
if (value !== emptyStateValue) {
Object.defineProperty(this, 'state', {
configurable: true,
enumerable: true,
value,
writable: true,
});
}
},
});
}
}
return withSetStateAllowed(() => renderElement(renderedEl, context));
}
},
unmount() {
renderer.unmount();
},
getNode() {
if (isDOM) {
return elementToTree(cachedNode);
}
const output = renderer.getRenderOutput();
return {
nodeType: nodeTypeFromType(cachedNode.type),
type: cachedNode.type,
props: cachedNode.props,
key: ensureKeyOrUndefined(cachedNode.key),
ref: cachedNode.ref,
instance: renderer._instance,
rendered: Array.isArray(output)
? flatten(output).map((el) => elementToTree(el))
: elementToTree(output),
};
},
simulateError(nodeHierarchy, rootNode, error) {
simulateError(
error,
renderer._instance,
cachedNode,
nodeHierarchy.concat(cachedNode),
nodeTypeFromType,
adapter.displayNameOfNode,
cachedNode.type,
);
},
simulateEvent(node, event, ...args) {
const handler = node.props[propFromEvent(event)];
if (handler) {
withSetStateAllowed(() => {
// TODO(lmr): create/use synthetic events
// TODO(lmr): emulate React's event propagation
handler(...args);
});
}
},
batchedUpdates(fn) {
return fn();
},
checkPropTypes(typeSpecs, values, location, hierarchy) {
return checkPropTypes(typeSpecs, values, location, displayNameOfNode(cachedNode), () =>
getComponentStack(hierarchy.concat([cachedNode])),
);
},
};
}
createStringRenderer(options) {
if (has(options, 'suspenseFallback')) {
throw new TypeError(
'`suspenseFallback` should not be specified in options of string renderer',
);
}
return {
render(el, context) {
if (options.context && (el.type.contextTypes || options.childContextTypes)) {
const childContextTypes = {
...(el.type.contextTypes || {}),
...options.childContextTypes,
};
const ContextWrapper = createRenderWrapper(el, context, childContextTypes);
return ReactDOMServer.renderToStaticMarkup(React.createElement(ContextWrapper));
}
return ReactDOMServer.renderToStaticMarkup(el);
},
};
}
// Provided a bag of options, return an `EnzymeRenderer`. Some options can be implementation
// specific, like `attach` etc. for React, but not part of this interface explicitly.
createRenderer(options) {
switch (options.mode) {
case EnzymeAdapter.MODES.MOUNT:
return this.createMountRenderer(options);
case EnzymeAdapter.MODES.SHALLOW:
return this.createShallowRenderer(options);
case EnzymeAdapter.MODES.STRING:
return this.createStringRenderer(options);
default:
throw new Error(`Enzyme Internal Error: Unrecognized mode: ${options.mode}`);
}
}
wrap(element) {
return wrap(element);
}
// converts an RSTNode to the corresponding JSX Pragma Element. This will be needed
// in order to implement the `Wrapper.mount()` and `Wrapper.shallow()` methods, but should
// be pretty straightforward for people to implement.
nodeToElement(node) {
if (!node || typeof node !== 'object') return null;
const { type } = node;
return React.createElement(unmemoType(type), propsWithKeysAndRef(node));
}
matchesElementType(node, matchingType) {
if (!node) {
return node;
}
const { type } = node;
return unmemoType(type) === unmemoType(matchingType);
}
elementToNode(element) {
return elementToTree(element);
}
nodeToHostNode(node, supportsArray = false) {
const nodes = nodeToHostNode(node);
if (Array.isArray(nodes) && !supportsArray) {
// get the first non-null node
return nodes.filter(Boolean)[0];
}
return nodes;
}
displayNameOfNode(node) {
if (!node) return null;
const { type, $$typeof } = node;
const adapter = this;
const nodeType = type || $$typeof;
// newer node types may be undefined, so only test if the nodeType exists
if (nodeType) {
switch (nodeType) {
case ConcurrentMode || NaN:
return 'ConcurrentMode';
case Fragment || NaN:
return 'Fragment';
case StrictMode || NaN:
return 'StrictMode';
case Profiler || NaN:
return 'Profiler';
case Portal || NaN:
return 'Portal';
case Suspense || NaN:
return 'Suspense';
default:
}
}
const $$typeofType = type && type.$$typeof;
switch ($$typeofType) {
case ContextConsumer || NaN:
return 'ContextConsumer';
case ContextProvider || NaN:
return 'ContextProvider';
case Memo || NaN: {
const nodeName = displayNameOfNode(node);
return typeof nodeName === 'string' ? nodeName : `Memo(${adapter.displayNameOfNode(type)})`;
}
case ForwardRef || NaN: {
if (type.displayName) {
return type.displayName;
}
const name = adapter.displayNameOfNode({ type: type.render });
return name ? `ForwardRef(${name})` : 'ForwardRef';
}
case Lazy || NaN: {
return 'lazy';
}
default:
return displayNameOfNode(node);
}
}
isValidElement(element) {
return isElement(element);
}
isValidElementType(object) {
return !!object && isValidElementType(object);
}
isFragment(fragment) {
return typeOfNode(fragment) === Fragment;
}
isCustomComponent(type) {
const fakeElement = makeFakeElement(type);
return (
!!type &&
(typeof type === 'function' ||
isForwardRef(fakeElement) ||
isContextProvider(fakeElement) ||
isContextConsumer(fakeElement) ||
isSuspense(fakeElement))
);
}
isContextConsumer(type) {
return !!type && isContextConsumer(makeFakeElement(type));
}
isCustomComponentElement(inst) {
if (!inst || !this.isValidElement(inst)) {
return false;
}
return this.isCustomComponent(inst.type);
}
getProviderFromConsumer(Consumer) {
// React stores references to the Provider on a Consumer differently across versions.
if (Consumer) {
let Provider;
if (Consumer._context) {
// check this first, to avoid a deprecation warning
({ Provider } = Consumer._context);
} else if (Consumer.Provider) {
({ Provider } = Consumer);
}
if (Provider) {
return Provider;
}
}
throw new Error('Enzyme Internal Error: can’t figure out how to get Provider from Consumer');
}
createElement(...args) {
return wrapAct(() => React.createElement(...args));
}
wrapWithWrappingComponent(node, options) {
return {
RootFinder,
node: wrapWithWrappingComponent(React.createElement, node, options),
};
}
}
module.exports = ReactEighteenAdapter;