enzyme-adapter-preact-pure
Version:
Enzyme adapter for Preact
176 lines (175 loc) • 6.27 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNode = exports.rstNodeFromElement = exports.nodeTypeFromType = void 0;
const preact_1 = require("preact");
const compat_js_1 = require("./compat.js");
const preact10_internals_js_1 = require("./preact10-internals.js");
const shallow_render_utils_js_1 = require("./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 === preact_1.Fragment) {
return rstNodesFromChildren((0, preact10_internals_js_1.getChildren)(node));
}
const component = (0, preact10_internals_js_1.getComponent)(node);
if (component) {
return rstNodeFromComponent(node, component);
}
if (!(0, preact10_internals_js_1.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: (0, preact10_internals_js_1.getDOMNode)(node),
rendered: rstNodesFromChildren((0, preact10_internals_js_1.getChildren)(node)),
};
}
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}`);
}
}
exports.nodeTypeFromType = nodeTypeFromType;
/**
* Convert a JSX element tree returned by Preact's `h` function into an RST
* node.
*/
function rstNodeFromElement(node, preserveChildrenProp = false) {
if (!(0, preact_1.isValidElement)(node)) {
return node;
}
const children = (0, compat_js_1.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: (0, preact10_internals_js_1.getComponent)(node),
rendered: children,
};
}
exports.rstNodeFromElement = rstNodeFromElement;
/**
* Return a React Standard Tree (RST) node from a Preact `Component` instance.
*/
function rstNodeFromComponent(vnode, component) {
const nodeType = nodeTypeFromType(component.constructor);
const rendered = rstNodesFromChildren((0, preact10_internals_js_1.getLastRenderOutput)(component));
// If this was a shallow-rendered component, set the RST node's type to the
// real component function/class.
const shallowRenderedType = (0, shallow_render_utils_js_1.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.
*/
function getNode(container) {
const rendered = (0, preact10_internals_js_1.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;
}
}
exports.getNode = getNode;