Skip to content

Commit

Permalink
Merge e60788c into 0a17404
Browse files Browse the repository at this point in the history
  • Loading branch information
chenesan committed Apr 21, 2019
2 parents 0a17404 + e60788c commit fec9497
Show file tree
Hide file tree
Showing 9 changed files with 519 additions and 15 deletions.
5 changes: 3 additions & 2 deletions docs/api/shallow.md
Expand Up @@ -50,8 +50,9 @@ describe('<MyComponent />', () => {
- `options.disableLifecycleMethods`: (`Boolean` [optional]): If set to true, `componentDidMount`
is not called on the component, and `componentDidUpdate` is not called after
[`setProps`](ShallowWrapper/setProps.md) and [`setContext`](ShallowWrapper/setContext.md). Default to `false`.
- `options.wrappingComponent`: (`ComponentType` [optional]): A component that will render as a parent of the `node`. It can be used to provide context to the `node`, among other things. See the [`getWrappingComponent()` docs](ShallowWrapper/getWrappingComponent.md) for an example. **Note**: `wrappingComponent` _must_ render its children.
- `options.wrappingComponentProps`: (`Object` [optional]): Initial props to pass to the `wrappingComponent` if it is specified.
- `options.wrappingComponent`: (`ComponentType` [optional]): A component that will render as a parent of the `node`. It can be used to provide context to the `node`, among other things. See the [`getWrappingComponent()` docs](ShallowWrapper/getWrappingComponent.md) for an example. **Note**: `wrappingComponent` _must_ render its children.
- `options.wrappingComponentProps`: (`Object` [optional]): Initial props to pass to the `wrappingComponent` if it is specified.
- `options.suspenseFallback`: (`Boolean` [optional]): If set to true, when rendering `Suspense` enzyme will replace all the lazy components in children with `fallback` element prop. Otherwise it won't handle fallback of lazy component. Default to `true`. Note: not supported in React < 16.6.

#### Returns

Expand Down
74 changes: 69 additions & 5 deletions packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
Expand Up @@ -10,6 +10,7 @@ import { version as testRendererVersion } from 'react-test-renderer/package.json
import TestUtils from 'react-dom/test-utils';
import semver from 'semver';
import checkPropTypes from 'prop-types/checkPropTypes';
import has from 'has';
import {
AsyncMode,
ConcurrentMode,
Expand All @@ -22,13 +23,17 @@ import {
isContextProvider,
isElement,
isForwardRef,
isLazy,
isMemo,
isPortal,
isSuspense,
isValidElementType,
Lazy,
Memo,
Portal,
Profiler,
StrictMode,
Suspense,
} from 'react-is';
import { EnzymeAdapter } from 'enzyme';
import { typeOfNode } from 'enzyme/build/Utils';
Expand Down Expand Up @@ -228,6 +233,19 @@ function toTree(vnode) {
rendered: childrenToTree(node.child),
};
}
case FiberTags.Suspense: {
return {
nodeType: 'function',
type: Suspense,
props: { ...node.memoizedProps },
key: ensureKeyOrUndefined(node.key),
ref: node.ref,
instance: null,
rendered: childrenToTree(node.child),
};
}
case FiberTags.Lazy:
return childrenToTree(node.child);
default:
throw new Error(`Enzyme Internal Error: unknown node with tag ${node.tag}`);
}
Expand Down Expand Up @@ -275,6 +293,25 @@ function nodeToHostNode(_node) {
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)) {
return fallback;
}
return {
...node,
props: {
...node.props,
children: replaceLazyWithFallback(node.props.children, fallback),
},
};
}

const eventOptions = {
animation: true,
pointerEvents: is164,
Expand Down Expand Up @@ -351,6 +388,9 @@ class ReactSixteenAdapter extends EnzymeAdapter {

createMountRenderer(options) {
assertDomAvailable('mount');
if (has(options, 'suspenseFallback')) {
throw new TypeError('`suspenseFallback` is not supported by the `mount` renderer');
}
if (FiberTags === null) {
// Requires DOM.
FiberTags = detectFiberTags();
Expand Down Expand Up @@ -445,9 +485,13 @@ class ReactSixteenAdapter extends EnzymeAdapter {
};
}

createShallowRenderer(/* options */) {
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;

Expand Down Expand Up @@ -498,8 +542,20 @@ class ReactSixteenAdapter extends EnzymeAdapter {
return withSetStateAllowed(() => renderer.render({ ...el, type: MockConsumer }));
} else {
isDOM = false;
const { type: Component } = el;

let renderedEl = el;
if (isLazy(renderedEl)) {
throw TypeError('`React.lazy` is not supported by shallow rendering.');
}
if (isSuspense(renderedEl)) {
let { children } = renderedEl.props;
if (suspenseFallback) {
const { fallback } = renderedEl.props;
children = replaceLazyWithFallback(children, fallback);
}
const FakeSuspenseWrapper = () => children;
renderedEl = React.createElement(FakeSuspenseWrapper, null, children);
}
const { type: Component } = renderedEl;
const isStateful = Component.prototype && (
Component.prototype.isReactComponent
|| Array.isArray(Component.__reactAutoBindPairs) // fallback for createClass components
Expand All @@ -517,7 +573,7 @@ class ReactSixteenAdapter extends EnzymeAdapter {

if (!isStateful && typeof Component === 'function') {
return withSetStateAllowed(() => renderer.render(
{ ...el, type: wrapFunctionalComponent(Component) },
{ ...renderedEl, type: wrapFunctionalComponent(Component) },
context,
));
}
Expand Down Expand Up @@ -546,7 +602,7 @@ class ReactSixteenAdapter extends EnzymeAdapter {
});
}
}
return withSetStateAllowed(() => renderer.render(el, context));
return withSetStateAllowed(() => renderer.render(renderedEl, context));
}
},
unmount() {
Expand Down Expand Up @@ -609,6 +665,9 @@ class ReactSixteenAdapter extends EnzymeAdapter {
}

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)) {
Expand Down Expand Up @@ -676,6 +735,7 @@ class ReactSixteenAdapter extends EnzymeAdapter {
case StrictMode || NaN: return 'StrictMode';
case Profiler || NaN: return 'Profiler';
case Portal || NaN: return 'Portal';
case Suspense || NaN: return 'Suspense';
default:
}
}
Expand All @@ -693,6 +753,9 @@ class ReactSixteenAdapter extends EnzymeAdapter {
const name = displayNameOfNode({ type: type.render });
return name ? `ForwardRef(${name})` : 'ForwardRef';
}
case Lazy || NaN: {
return 'lazy';
}
default: return displayNameOfNode(node);
}
}
Expand All @@ -716,6 +779,7 @@ class ReactSixteenAdapter extends EnzymeAdapter {
|| isForwardRef(fakeElement)
|| isContextProvider(fakeElement)
|| isContextConsumer(fakeElement)
|| isSuspense(fakeElement)
);
}

Expand Down
37 changes: 37 additions & 0 deletions packages/enzyme-adapter-react-16/src/detectFiberTags.js
@@ -1,5 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { fakeDynamicImport } from 'enzyme-adapter-utils';

function getFiber(element) {
const container = global.document.createElement('div');
Expand All @@ -14,12 +15,38 @@ function getFiber(element) {
return inst._reactInternalFiber.child;
}

function getLazyFiber(LazyComponent) {
const container = global.document.createElement('div');
let inst = null;
// eslint-disable-next-line react/prefer-stateless-function
class Tester extends React.Component {
render() {
inst = this;
return React.createElement(LazyComponent);
}
}
// eslint-disable-next-line react/prefer-stateless-function
class SuspenseWrapper extends React.Component {
render() {
return React.createElement(
React.Suspense,
{ fallback: false },
React.createElement(Tester),
);
}
}
ReactDOM.render(React.createElement(SuspenseWrapper), container);
return inst._reactInternalFiber.child;
}

module.exports = function detectFiberTags() {
const supportsMode = typeof React.StrictMode !== 'undefined';
const supportsContext = typeof React.createContext !== 'undefined';
const supportsForwardRef = typeof React.forwardRef !== 'undefined';
const supportsMemo = typeof React.memo !== 'undefined';
const supportsProfiler = typeof React.unstable_Profiler !== 'undefined';
const supportsSuspense = typeof React.Suspense !== 'undefined';
const supportsLazy = typeof React.lazy !== 'undefined';

function Fn() {
return null;
Expand All @@ -32,6 +59,7 @@ module.exports = function detectFiberTags() {
}
let Ctx = null;
let FwdRef = null;
let LazyComponent = null;
if (supportsContext) {
Ctx = React.createContext();
}
Expand All @@ -40,6 +68,9 @@ module.exports = function detectFiberTags() {
// eslint-disable-next-line no-unused-vars
FwdRef = React.forwardRef((props, ref) => null);
}
if (supportsLazy) {
LazyComponent = React.lazy(() => fakeDynamicImport(() => null));
}

return {
HostRoot: getFiber('test').return.return.tag, // Go two levels above to find the root
Expand Down Expand Up @@ -70,5 +101,11 @@ module.exports = function detectFiberTags() {
Profiler: supportsProfiler
? getFiber(React.createElement(React.unstable_Profiler, { id: 'mock', onRender() {} })).tag
: -1,
Suspense: supportsSuspense
? getFiber(React.createElement(React.Suspense, { fallback: false })).tag
: -1,
Lazy: supportsLazy
? getLazyFiber(LazyComponent).tag
: -1,
};
};
4 changes: 4 additions & 0 deletions packages/enzyme-adapter-utils/src/Utils.js
Expand Up @@ -363,3 +363,7 @@ export function getWrappingComponentMountRenderer({ toTree, getMountWrapperInsta
},
};
}

export function fakeDynamicImport(moduleToImport) {
return Promise.resolve({ default: moduleToImport });
}
26 changes: 21 additions & 5 deletions packages/enzyme-test-suite/test/Adapter-spec.jsx
Expand Up @@ -9,20 +9,22 @@ import {
} from 'react-is';
import PropTypes from 'prop-types';
import wrap from 'mocha-wrap';
import { wrapWithWrappingComponent, RootFinder } from 'enzyme-adapter-utils';
import { fakeDynamicImport, wrapWithWrappingComponent, RootFinder } from 'enzyme-adapter-utils';

import './_helpers/setupAdapters';
import Adapter from './_helpers/adapter';
import {
renderToString,
AsyncMode,
ConcurrentMode,
createContext,
createPortal,
forwardRef,
Fragment,
StrictMode,
AsyncMode,
ConcurrentMode,
lazy,
Profiler,
renderToString,
StrictMode,
Suspense,
} from './_helpers/react-compat';
import { is } from './_helpers/version';
import { itIf, describeWithDOM, describeIf } from './_helpers';
Expand Down Expand Up @@ -1063,6 +1065,20 @@ describe('Adapter', () => {
itIf(is('>= 16.6'), 'supports ConcurrentMode', () => {
expect(getDisplayName(<ConcurrentMode />)).to.equal('ConcurrentMode');
});

itIf(is('>= 16.6'), 'supports Suspense', () => {
expect(getDisplayName(<Suspense />)).to.equal('Suspense');
});

itIf(is('>= 16.6'), 'supports lazy', () => {
class DynamicComponent extends React.Component {
render() {
return <div>DynamicComponent</div>;
}
}
const LazyComponent = lazy(() => fakeDynamicImport(DynamicComponent));
expect(getDisplayName(<LazyComponent />)).to.equal('lazy');
});
});

describeIf(is('>= 16.2'), 'determines if node isFragment', () => {
Expand Down

0 comments on commit fec9497

Please sign in to comment.