diff --git a/docs/api/shallow.md b/docs/api/shallow.md
index 0756bb06b..53ee3f86a 100644
--- a/docs/api/shallow.md
+++ b/docs/api/shallow.md
@@ -50,8 +50,9 @@ describe('', () => {
- `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
diff --git a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
index 2db7317e6..5ead193ce 100644
--- a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
+++ b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
@@ -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,
@@ -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';
@@ -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}`);
}
@@ -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,
@@ -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();
@@ -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;
@@ -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
@@ -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,
));
}
@@ -546,7 +602,7 @@ class ReactSixteenAdapter extends EnzymeAdapter {
});
}
}
- return withSetStateAllowed(() => renderer.render(el, context));
+ return withSetStateAllowed(() => renderer.render(renderedEl, context));
}
},
unmount() {
@@ -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)) {
@@ -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:
}
}
@@ -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);
}
}
@@ -716,6 +779,7 @@ class ReactSixteenAdapter extends EnzymeAdapter {
|| isForwardRef(fakeElement)
|| isContextProvider(fakeElement)
|| isContextConsumer(fakeElement)
+ || isSuspense(fakeElement)
);
}
diff --git a/packages/enzyme-adapter-react-16/src/detectFiberTags.js b/packages/enzyme-adapter-react-16/src/detectFiberTags.js
index f1368da17..92c3cce84 100644
--- a/packages/enzyme-adapter-react-16/src/detectFiberTags.js
+++ b/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');
@@ -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;
@@ -32,6 +59,7 @@ module.exports = function detectFiberTags() {
}
let Ctx = null;
let FwdRef = null;
+ let LazyComponent = null;
if (supportsContext) {
Ctx = React.createContext();
}
@@ -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
@@ -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,
};
};
diff --git a/packages/enzyme-adapter-utils/src/Utils.js b/packages/enzyme-adapter-utils/src/Utils.js
index b9a27df87..83671c99d 100644
--- a/packages/enzyme-adapter-utils/src/Utils.js
+++ b/packages/enzyme-adapter-utils/src/Utils.js
@@ -363,3 +363,7 @@ export function getWrappingComponentMountRenderer({ toTree, getMountWrapperInsta
},
};
}
+
+export function fakeDynamicImport(moduleToImport) {
+ return Promise.resolve({ default: moduleToImport });
+}
diff --git a/packages/enzyme-test-suite/test/Adapter-spec.jsx b/packages/enzyme-test-suite/test/Adapter-spec.jsx
index 8f083953a..016d2505c 100644
--- a/packages/enzyme-test-suite/test/Adapter-spec.jsx
+++ b/packages/enzyme-test-suite/test/Adapter-spec.jsx
@@ -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';
@@ -1063,6 +1065,20 @@ describe('Adapter', () => {
itIf(is('>= 16.6'), 'supports ConcurrentMode', () => {
expect(getDisplayName()).to.equal('ConcurrentMode');
});
+
+ itIf(is('>= 16.6'), 'supports Suspense', () => {
+ expect(getDisplayName()).to.equal('Suspense');
+ });
+
+ itIf(is('>= 16.6'), 'supports lazy', () => {
+ class DynamicComponent extends React.Component {
+ render() {
+ return
DynamicComponent
;
+ }
+ }
+ const LazyComponent = lazy(() => fakeDynamicImport(DynamicComponent));
+ expect(getDisplayName()).to.equal('lazy');
+ });
});
describeIf(is('>= 16.2'), 'determines if node isFragment', () => {
diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
index f61d2e591..bddcc7988 100644
--- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
+++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
@@ -15,17 +15,20 @@ import {
withSetStateAllowed,
} from 'enzyme/build/Utils';
import getAdapter from 'enzyme/build/getAdapter';
+import { fakeDynamicImport } from 'enzyme-adapter-utils';
import './_helpers/setupAdapters';
import {
createClass,
createContext,
createPortal,
- Fragment,
forwardRef,
+ Fragment,
+ lazy,
memo,
Profiler,
PureComponent,
+ Suspense,
useEffect,
useState,
} from './_helpers/react-compat';
@@ -34,6 +37,7 @@ import {
describeIf,
itIf,
} from './_helpers';
+import getLoadedLazyComponent from './_helpers/getLoadedLazyComponent';
import describeMethods from './_helpers/describeMethods';
import {
is,
@@ -1078,6 +1082,132 @@ describeWithDOM('mount', () => {
'wrap',
);
+ describeIf(is('>= 16.6'), 'Suspense & lazy', () => {
+ class DynamicComponent extends React.Component {
+ render() {
+ return (
+ Dynamic Component
+ );
+ }
+ }
+ class Fallback extends React.Component {
+ render() {
+ return (
+ Fallback
+ );
+ }
+ }
+ it('finds Suspense and its children when no lazy component', () => {
+ class Component extends React.Component {
+ render() {
+ return (
+ test
+ );
+ }
+ }
+
+ const SuspenseComponent = () => (
+
+
+
+ );
+
+ const wrapper = mount();
+
+ expect(wrapper.is(SuspenseComponent)).to.equal(true);
+ expect(wrapper.find(Component)).to.have.lengthOf(1);
+ expect(wrapper.find(Fallback)).to.have.lengthOf(0);
+ });
+
+ it('can mount Suspense directly', () => {
+ const wrapper = mount();
+ expect(wrapper.is(Suspense)).to.equal(true);
+ });
+
+ it('finds fallback when given lazy component in initial mount', () => {
+ const LazyComponent = lazy(() => fakeDynamicImport(DynamicComponent));
+ const SuspenseComponent = () => (
+ }>
+
+
+ );
+
+ const wrapper = mount();
+
+ expect(wrapper.is(SuspenseComponent)).to.equal(true);
+ expect(wrapper.find(LazyComponent)).to.have.lengthOf(0);
+ expect(wrapper.find(Fallback)).to.have.lengthOf(1);
+ });
+
+ it('return fallback string when given lazy component in initial mount and call .debug()', () => {
+ const LazyComponent = lazy(() => fakeDynamicImport(DynamicComponent));
+ const SuspenseComponent = () => (
+ }>
+
+
+ );
+
+ const wrapper = mount();
+
+ expect(wrapper.debug()).to.equal(`
+
+
+
+ Fallback
+
+
+
+`);
+ });
+
+ it('return wrapped component when given loaded lazy component in initial mount', () => {
+ const LazyComponent = getLoadedLazyComponent(DynamicComponent);
+ const SuspenseComponent = () => (
+ }>
+
+
+ );
+
+ const wrapper = mount();
+
+ expect(wrapper.is(SuspenseComponent)).to.equal(true);
+ expect(wrapper.find(LazyComponent)).to.have.lengthOf(0);
+ expect(wrapper.find(DynamicComponent)).to.have.lengthOf(1);
+ expect(wrapper.find(Fallback)).to.have.lengthOf(0);
+ });
+
+ it('return wrapped component string when given loaded lazy component in initial mount and call .debug()', () => {
+ const LazyComponent = getLoadedLazyComponent(DynamicComponent);
+ const SuspenseComponent = () => (
+ }>
+
+
+ );
+
+ const wrapper = mount();
+
+ expect(wrapper.debug()).to.equal(`
+
+
+
+ Dynamic Component
+
+
+
+`);
+ });
+
+ it('throws if options.suspenseFallback is specified', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+ const SuspenseComponent = () => (
+ }>
+
+
+ );
+ expect(() => mount(, { suspenseFallback: false })).to.throw();
+ });
+ });
+
describe('.mount()', () => {
it('calls componentWillUnmount()', () => {
const willMount = sinon.spy();
diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
index 253b11de1..7c58d2025 100644
--- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
+++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
@@ -14,15 +14,18 @@ import {
withSetStateAllowed,
} from 'enzyme/build/Utils';
import getAdapter from 'enzyme/build/getAdapter';
+import { fakeDynamicImport } from 'enzyme-adapter-utils';
import './_helpers/setupAdapters';
import {
createClass,
createContext,
createPortal,
- Fragment,
forwardRef,
+ Fragment,
+ lazy,
PureComponent,
+ Suspense,
useEffect,
useState,
Profiler,
@@ -1591,6 +1594,209 @@ describe('shallow', () => {
});
});
+ describeIf(is('>= 16.6'), 'Suspense & lazy', () => {
+ class DynamicComponent extends React.Component {
+ render() {
+ return (
+ Dynamic Component
+ );
+ }
+ }
+
+ class Fallback extends React.Component {
+ render() {
+ return (
+ Fallback
+ );
+ }
+ }
+
+ it('finds Suspense and its children when no lazy component', () => {
+ class Component extends React.Component {
+ render() {
+ return (
+ test
+ );
+ }
+ }
+
+ const SuspenseComponent = () => (
+ }>
+
+
+ );
+
+ const wrapper = shallow();
+
+ expect(wrapper.is(Suspense)).to.equal(true);
+ expect(wrapper.find(Component)).to.have.lengthOf(1);
+ expect(wrapper.find(Fallback)).to.have.lengthOf(0);
+ });
+
+ it('finds LazyComponent when render component wrapping lazy component', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+ const SuspenseComponent = () => (
+ }>
+
+
+ );
+
+ const wrapper = shallow();
+
+ expect(wrapper.is(Suspense)).to.equal(true);
+ expect(wrapper.find(LazyComponent)).to.have.lengthOf(1);
+ expect(wrapper.find(Fallback)).to.have.lengthOf(0);
+ });
+
+ it('returns suspense and lazy component string when debug() is called', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+ const SuspenseComponent = () => (
+ }>
+
+
+ );
+
+ const wrapper = shallow();
+
+ expect(wrapper.debug()).to.equal(`
+
+`);
+ });
+
+ it('renders lazy component when render Suspense without option', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+
+ const wrapper = shallow((
+ }>
+
+
+ ));
+
+ expect(wrapper.find(LazyComponent)).to.have.lengthOf(1);
+ expect(wrapper.find(Fallback)).to.have.lengthOf(0);
+ });
+
+ it('returns lazy component string when debug() is called', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+
+ const wrapper = shallow((
+ }>
+
+
+ ));
+
+ expect(wrapper.debug()).to.equal('');
+ });
+
+ it('replaces LazyComponent with Fallback when render Suspense if options.suspenseFallback=true', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+
+ const wrapper = shallow((
+ }>
+
+
+ ), { suspenseFallback: true });
+
+ expect(wrapper.find(LazyComponent)).to.have.lengthOf(0);
+ expect(wrapper.find(Fallback)).to.have.lengthOf(1);
+ });
+
+ it('returns fallback component string when debug() is called if options.suspenseFallback=true', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+
+ const wrapper = shallow((
+ }>
+
+
+ ), { suspenseFallback: true });
+
+ expect(wrapper.debug()).to.equal('');
+ });
+
+ it('throws if options.suspenseFallback is not boolean or undefined', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+ const SuspenseComponent = () => (
+ }>
+
+
+ );
+ expect(() => shallow(, { suspenseFallback: 'true' })).to.throw();
+ });
+
+ it('finds fallback after dive into functional component wrapping Suspense', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+ const SuspenseComponent = () => (
+ }>
+
+
+ );
+
+ const wrapper = shallow(, { suspenseFallback: true });
+ const inner = wrapper.dive();
+
+ expect(inner.find(LazyComponent)).to.have.lengthOf(0);
+ expect(inner.find(Fallback)).to.have.lengthOf(1);
+ });
+
+ it('replaces nested LazyComponent with Fallback when render Suspense with options.suspenseFallback=true', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+
+ const wrapper = shallow((
+ }>
+
+
+
+
+ ), { suspenseFallback: true });
+
+ expect(wrapper.find(LazyComponent)).to.have.lengthOf(0);
+ expect(wrapper.find(Fallback)).to.have.lengthOf(2);
+ expect(wrapper.find('.should-be-rendered')).to.have.lengthOf(2);
+ expect(wrapper.find('.should-be-rendered > .inner')).to.have.lengthOf(1);
+ });
+
+ it('does not replace LazyComponent with Fallback when render Suspense if options.suspenseFallback = false', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+
+ const wrapper = shallow((
+ }>
+
+
+ ), { suspenseFallback: false });
+
+ expect(wrapper.find(LazyComponent)).to.have.lengthOf(1);
+ expect(wrapper.find(Fallback)).to.have.lengthOf(0);
+ });
+
+ it('does not replace nested LazyComponent with Fallback when render Suspense if option.suspenseFallback = false', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+
+ const wrapper = shallow((
+ }>
+
+
+
+
+ ), { suspenseFallback: false });
+
+ expect(wrapper.find(LazyComponent)).to.have.lengthOf(2);
+ expect(wrapper.find(Fallback)).to.have.lengthOf(0);
+ expect(wrapper.find('.should-be-rendered')).to.have.lengthOf(2);
+ expect(wrapper.find('.should-be-rendered > .inner')).to.have.lengthOf(1);
+ });
+
+ it('throws when rendering lazy component', () => {
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+ expect(() => shallow()).to.throw();
+ });
+ });
+
describe('lifecycle methods', () => {
describe('disableLifecycleMethods option', () => {
describe('validation', () => {
diff --git a/packages/enzyme-test-suite/test/_helpers/getLoadedLazyComponent.js b/packages/enzyme-test-suite/test/_helpers/getLoadedLazyComponent.js
new file mode 100644
index 000000000..d9ca7765e
--- /dev/null
+++ b/packages/enzyme-test-suite/test/_helpers/getLoadedLazyComponent.js
@@ -0,0 +1,31 @@
+import { fakeDynamicImport } from 'enzyme-adapter-utils';
+import { lazy } from './react-compat';
+import { is, VERSION } from './version';
+
+function fakeSyncThenable(result) {
+ return {
+ then(resolve) {
+ return resolve({ default: result });
+ },
+ };
+}
+
+export default function getLoadedLazyComponent(wrappedComponent) {
+ if (is('>= 16.8')) {
+ return lazy(() => fakeSyncThenable(wrappedComponent));
+ }
+ if (is('>= 16.6')) {
+ const LazyComponent = lazy(() => fakeDynamicImport(wrappedComponent));
+ /**
+ * Before React v16.8 there's no public api to synchronously / await
+ * loaded lazy component.
+ * So we have to hack this by setting `_result` and `_status` implementation.
+ */
+ /* eslint-disable no-underscore-dangle */
+ LazyComponent._result = wrappedComponent;
+ /* eslint-disable no-underscore-dangle */
+ LazyComponent._status = 1;
+ return LazyComponent;
+ }
+ throw Error(`Current React version ${VERSION} doesn't support \`lazy()\` api.`);
+}
diff --git a/packages/enzyme-test-suite/test/adapter-utils-spec.jsx b/packages/enzyme-test-suite/test/adapter-utils-spec.jsx
index be23428d1..976508588 100644
--- a/packages/enzyme-test-suite/test/adapter-utils-spec.jsx
+++ b/packages/enzyme-test-suite/test/adapter-utils-spec.jsx
@@ -12,6 +12,7 @@ import {
getNodeFromRootFinder,
wrapWithWrappingComponent,
getWrappingComponentMountRenderer,
+ fakeDynamicImport,
} from 'enzyme-adapter-utils';
import './_helpers/setupAdapters';
@@ -378,4 +379,22 @@ describe('enzyme-adapter-utils', () => {
});
});
});
+
+ describe('fakeDynamicImport', () => {
+ it('is a function', () => {
+ expect(fakeDynamicImport).to.be.a('function');
+ });
+
+ it('returns a promise', () => {
+ const promise = fakeDynamicImport();
+ expect(Promise.resolve(promise)).to.equal(promise);
+ });
+
+ it('returns a promise for an object containing the provided argument', () => {
+ const signal = {};
+ return fakeDynamicImport(signal).then((actual) => {
+ expect(actual).to.have.property('default', signal);
+ });
+ });
+ });
});
diff --git a/packages/enzyme-test-suite/test/staticRender-spec.jsx b/packages/enzyme-test-suite/test/staticRender-spec.jsx
index 7484828be..e130c1352 100644
--- a/packages/enzyme-test-suite/test/staticRender-spec.jsx
+++ b/packages/enzyme-test-suite/test/staticRender-spec.jsx
@@ -3,11 +3,12 @@ import PropTypes from 'prop-types';
import { expect } from 'chai';
import { render } from 'enzyme';
import renderEntry from 'enzyme/render';
+import { fakeDynamicImport } from 'enzyme-adapter-utils';
import './_helpers/setupAdapters';
import { describeWithDOM, describeIf } from './_helpers';
import { is } from './_helpers/version';
-import { createClass } from './_helpers/react-compat';
+import { createClass, lazy } from './_helpers/react-compat';
describeWithDOM('render', () => {
describe('top level entry points', () => {
@@ -80,4 +81,18 @@ describeWithDOM('render', () => {
expect(() => render(, { context })).to.not.throw(Error);
});
});
+
+ describeIf(is('> 16.6'), 'suspense fallback option', () => {
+ it('throws if options.suspenseFallback is specified', () => {
+ class DynamicComponent extends React.Component {
+ render() {
+ return (
+ Dynamic Component
+ );
+ }
+ }
+ const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
+ expect(() => render(, { suspenseFallback: false })).to.throw();
+ });
+ });
});