enzyme-adapter-preact-pure
Version:
Enzyme adapter for Preact
143 lines (142 loc) • 5.72 kB
JavaScript
import enzyme from 'enzyme';
import { Fragment, cloneElement, h } from 'preact';
import MountRenderer from './MountRenderer.js';
import ShallowRenderer from './ShallowRenderer.js';
import StringRenderer from './StringRenderer.js';
import { childElements } from './compat.js';
import { rstNodeFromElement } from './preact10-rst.js';
import RootFinder from './RootFinder.js';
import { isRSTNode, nodeToHostNode } from './util.js';
export const { EnzymeAdapter } = enzyme;
export default class Adapter extends EnzymeAdapter {
constructor(preactAdapterOptions = {}) {
super();
// This function is only called during shallow rendering
this.wrapWithWrappingComponent = (el,
/**
* Tip:
* The use of `wrappingComponent` and `wrappingComponentProps` is discouraged.
* Using those props complicates a potential future migration to a different testing library.
* Instead, wrap a component like this:
* ```
* shallow(<Wrapper><Component/></Wrapper>).dive()
* ```
*/
options = {}) => {
const { wrappingComponent, wrappingComponentProps = {} } = options;
if (!wrappingComponent) {
return { RootFinder, node: el };
}
let elementWithValidChildren;
if (typeof el.props.children === 'string') {
// This prevents an error when `.dive()` is used:
// `TypeError: ShallowWrapper::dive() can only be called on components`.
// ---------------------------------------------------------------------
// VNode before: `{ type: Widget, props: { children: 'test' }, ... }`
// VNode after: `{ type: Widget, props: { children: ['test'] }, ... }`
elementWithValidChildren = cloneElement(el, el.props, childElements(el));
}
else {
elementWithValidChildren = el;
}
const wrappedElement = h(wrappingComponent, wrappingComponentProps, h(RootFinder, null, elementWithValidChildren));
return {
RootFinder,
node: wrappedElement,
};
};
this.preactAdapterOptions = preactAdapterOptions;
this.options = {
// Prevent Enzyme's shallow renderer from manually invoking lifecycle
// methods after a render. This manual invocation is needed for React
// but not for the Preact adapter because we re-use the normal rendering
// logic.
lifecycles: {
componentDidUpdate: {
onSetState: false,
},
},
};
// Work around a bug in Enzyme where `ShallowWrapper.getElements` calls
// the `nodeToElement` method with undefined `this`.
this.nodeToElement = this.nodeToElement.bind(this);
if (this.preactAdapterOptions.ShallowRenderer) {
this.isFragment = node => node?.type === Fragment;
this.displayNameOfNode = (node) => {
if (!node || !node.type) {
return null;
}
if (this.isFragment?.(node)) {
return 'Fragment';
}
return typeof node.type === 'function'
? node.type.displayName || node.type.name || 'Component'
: node.type;
};
}
}
createRenderer(options) {
switch (options.mode) {
case 'mount':
// The `attachTo` option is only supported for DOM rendering, for
// consistency with React, even though the Preact adapter could easily
// support it for shallow rendering.
return new MountRenderer({
...options,
...this.preactAdapterOptions,
container: options.attachTo,
});
case 'shallow':
if (this.preactAdapterOptions.ShallowRenderer) {
return new this.preactAdapterOptions.ShallowRenderer({
...this.preactAdapterOptions,
});
}
else {
return new ShallowRenderer({ ...this.preactAdapterOptions });
}
case 'string':
return new StringRenderer({ ...this.preactAdapterOptions });
default:
throw new Error(`"${options.mode}" rendering is not supported`);
}
}
nodeToElement(node) {
if (!isRSTNode(node)) {
return node;
}
const props = { ...node.props };
if (node.key) {
props.key = node.key;
}
if (node.ref) {
props.ref = node.ref;
}
const childElements = node.rendered.map(n => this.nodeToElement(n));
return h(node.type, props, ...childElements);
}
nodeToHostNode(node) {
return nodeToHostNode(node);
}
isValidElement(el) {
if (el == null || typeof el !== 'object') {
return false;
}
if (typeof el.type !== 'string' &&
typeof el.type !== 'function' &&
el.type !== null) {
return false;
}
if (typeof el.props !== 'object' || el.props == null) {
return false;
}
return true;
}
createElement(type, props, ...children) {
return h(type, props, ...children);
}
elementToNode(el) {
return rstNodeFromElement(el, Boolean(this.preactAdapterOptions.ShallowRenderer) // preserveChildrenProp
);
}
}