enzyme-adapter-preact-pure
Version:
Enzyme adapter for Preact
170 lines (169 loc) • 5.78 kB
JavaScript
/**
* Functions for rendering components using Preact v10 and converting the result
* to a React Standard Tree (RST) format defined by Enzyme.
*
* Preact 10 stores details of the rendered elements on internal fields of
* the VNodes. A reference to the vnode is stored in the root DOM element.
* The rendered result is converted to RST by traversing these vnode references.
*/
import { isValidElement, Fragment } from 'preact';
import { childElements } from './compat.js';
import { getChildren, getComponent, getDOMNode, getLastRenderOutput, getLastVNodeRenderedIntoContainer, } from './preact10-internals.js';
import { getRealType } from './shallow-render-utils.js';
function stripSpecialProps(props, preserveChildrenProp = false) {
if (preserveChildrenProp) {
const { key, ref, ...otherProps } = props;
return otherProps;
}
else {
const { children, key, ref, ...otherProps } = props;
return otherProps;
}
}
function convertDOMProps(props, preserveChildrenProp = false) {
const srcProps = stripSpecialProps(props, preserveChildrenProp);
const converted = {};
Object.keys(srcProps).forEach(srcProp => {
const destProp = srcProp === 'class' ? 'className' : srcProp;
converted[destProp] = props[srcProp];
});
return converted;
}
/**
* Convert the rendered output of a vnode to RST nodes.
*/
function rstNodesFromChildren(nodes) {
if (!nodes) {
return [];
}
return nodes.flatMap(node => {
if (node === null) {
// The array of rendered children may have `null` entries as a result of
// eg. conditionally rendered children where the condition was false.
//
// These are omitted from the rendered tree that Enzyme works with.
return [];
}
const rst = rstNodeFromVNode(node);
return Array.isArray(rst) ? rst : [rst];
});
}
function rstNodeFromVNode(node) {
if (node == null) {
return null;
}
// Preact 10 represents text nodes as VNodes with `node.type == null` and
// `node.props` equal to the string content.
if (typeof node.props === 'string' ||
typeof node.props === 'number' ||
typeof node.props === 'bigint') {
return String(node.props);
}
if (node.type === Fragment) {
return rstNodesFromChildren(getChildren(node));
}
const component = getComponent(node);
if (component) {
return rstNodeFromComponent(node, component);
}
if (!getDOMNode(node)) {
throw new Error(`Expected VDOM node to be a DOM node but got ${node.type}`);
}
return {
nodeType: 'host',
type: node.type,
props: convertDOMProps(node.props),
key: node.key || null,
ref: node.ref || null,
instance: getDOMNode(node),
rendered: rstNodesFromChildren(getChildren(node)),
};
}
export function nodeTypeFromType(type) {
if (typeof type === 'string') {
return 'host';
}
else if (type.prototype && typeof type.prototype.render === 'function') {
return 'class';
}
else if (typeof type === 'function') {
return 'function';
}
else {
throw new Error(`Unknown node type: ${type}`);
}
}
/**
* Convert a JSX element tree returned by Preact's `h` function into an RST
* node.
*/
export function rstNodeFromElement(node, preserveChildrenProp = false) {
if (!isValidElement(node)) {
return node;
}
const children = childElements(node)
.flat()
.map(child => rstNodeFromElement(child, preserveChildrenProp));
const nodeType = nodeTypeFromType(node.type);
let props = {};
if (typeof node.props === 'object' && node.props) {
props =
nodeType === 'host'
? convertDOMProps(node.props, preserveChildrenProp)
: stripSpecialProps(node.props, preserveChildrenProp);
}
const ref = node.ref || null;
return {
nodeType,
type: node.type,
props,
key: node.key || null,
ref,
instance: getComponent(node),
rendered: children,
};
}
/**
* Return a React Standard Tree (RST) node from a Preact `Component` instance.
*/
function rstNodeFromComponent(vnode, component) {
const nodeType = nodeTypeFromType(component.constructor);
const rendered = rstNodesFromChildren(getLastRenderOutput(component));
// If this was a shallow-rendered component, set the RST node's type to the
// real component function/class.
const shallowRenderedType = getRealType(component);
const type = shallowRenderedType
? shallowRenderedType
: component.constructor;
return {
nodeType,
type,
props: { children: [], ...component.props },
key: vnode.key || null,
ref: vnode.ref || null,
instance: component,
rendered,
};
}
/**
* Convert the Preact components rendered into `container` into an RST node.
*/
export function getNode(container) {
const rendered = getLastVNodeRenderedIntoContainer(container);
const rstNode = rstNodeFromVNode(rendered);
// There is currently a requirement that the root element produces a single
// RST node. Fragments do not appear in the RST tree, so it is fine if the
// root node is a fragment, provided that it renders only a single child. In
// fact Preact itself wraps the root element in a single-child fragment.
if (Array.isArray(rstNode)) {
if (rstNode.length === 1) {
return rstNode[0];
}
else {
throw new Error('Root element must not be a fragment with multiple children');
}
}
else {
return rstNode;
}
}