enzyme
Version:
JavaScript Testing utilities for React
208 lines (175 loc) • 5.21 kB
JavaScript
import flat from 'array.prototype.flat';
import entries from 'object.entries';
import isSubset from 'is-subset';
import functionName from 'function.prototype.name';
import isRegex from 'is-regex';
import getAdapter from './getAdapter';
export function propsOfNode(node) {
return (node && node.props) || {};
}
export function childrenOfNode(node) {
if (!node) return [];
const adapter = getAdapter();
const adapterHasIsFragment = adapter.isFragment && typeof adapter.isFragment === 'function';
const renderedArray = Array.isArray(node.rendered) ? flat(node.rendered, 1) : [node.rendered];
// React adapters before 16 will not have isFragment
if (!adapterHasIsFragment) {
return renderedArray;
}
return flat(renderedArray.map((currentChild) => {
// If the node is a Fragment, we want to return its children, not the fragment itself
if (adapter.isFragment(currentChild)) {
return childrenOfNode(currentChild);
}
return currentChild;
}), 1);
}
export function hasClassName(node, className) {
let classes = propsOfNode(node).className || '';
classes = String(classes).replace(/\s/g, ' ');
if (isRegex(className)) return className.test(classes);
return ` ${classes} `.indexOf(` ${className} `) > -1;
}
export function treeForEach(tree, fn) {
if (tree) {
fn(tree);
}
childrenOfNode(tree).forEach((node) => treeForEach(node, fn));
}
export function treeFilter(tree, fn) {
const results = [];
treeForEach(tree, (node) => {
if (fn(node)) {
results.push(node);
}
});
return results;
}
/**
* To support sibling selectors we need to be able to find
* the siblings of a node. The easiest way to do that is find
* the parent of the node and access its children.
*
* This would be unneeded if the RST spec included sibling pointers
* such as node.nextSibling and node.prevSibling
* @param {*} root
* @param {*} targetNode
*/
export function findParentNode(root, targetNode) {
const results = treeFilter(
root,
(node) => {
if (!node.rendered) {
return false;
}
return childrenOfNode(node).indexOf(targetNode) !== -1;
},
);
return results[0] || null;
}
function pathFilter(path, fn) {
return path.filter((tree) => treeFilter(tree, fn).length !== 0);
}
export function pathToNode(node, root) {
const queue = [root];
const path = [];
const hasNode = (testNode) => node === testNode;
while (queue.length) {
const current = queue.pop();
const children = childrenOfNode(current);
if (current === node) return pathFilter(path, hasNode);
path.push(current);
if (children.length === 0) {
// leaf node. if it isn't the node we are looking for, we pop.
path.pop();
}
queue.push(...children);
}
return null;
}
export function parentsOfNode(node, root) {
return (pathToNode(node, root) || []).reverse();
}
export function nodeHasId(node, id) {
return propsOfNode(node).id === id;
}
const CAN_NEVER_MATCH = {};
function replaceUndefined(v) {
return typeof v !== 'undefined' ? v : CAN_NEVER_MATCH;
}
function replaceUndefinedValues(obj) {
return entries(obj)
.reduce((acc, [k, v]) => ({ ...acc, [k]: replaceUndefined(v) }), {});
}
export function nodeMatchesObjectProps(node, props) {
return isSubset(propsOfNode(node), replaceUndefinedValues(props));
}
function getTextFromHostNode(hostNode) {
if (typeof hostNode === 'string') {
return String(hostNode || '');
}
if (!hostNode) {
return '';
}
return hostNode.textContent || '';
}
function getTextFromRSTNode(node, {
getCustom,
handleHostNodes,
recurse,
nullRenderReturnsNull = false,
}) {
if (node == null) {
return '';
}
if (typeof node === 'string' || typeof node === 'number') {
return String(node);
}
if (getCustom && node.type && typeof node.type === 'function') {
return getCustom(node);
}
if (handleHostNodes && node.nodeType === 'host') {
return handleHostNodes(node);
}
if (node.rendered == null && nullRenderReturnsNull) {
return null;
}
return childrenOfNode(node).map(recurse).join('');
}
export function getTextFromNode(node) {
return getTextFromRSTNode(node, {
recurse: getTextFromNode,
getCustom({ type }) {
return `<${type.displayName || functionName(type)} />`;
},
});
}
export function getTextFromHostNodes(node, adapter) {
return getTextFromRSTNode(node, {
recurse(item) {
return getTextFromHostNodes(item, adapter);
},
handleHostNodes(item) {
const nodes = [].concat(adapter.nodeToHostNode(item, true));
return nodes.map(getTextFromHostNode).join('');
},
});
}
function getHTMLFromHostNode(hostNode) {
if (hostNode == null) {
return null;
}
return hostNode.outerHTML.replace(/\sdata-(reactid|reactroot)+="([^"]*)+"/g, '');
}
export function getHTMLFromHostNodes(node, adapter) {
return getTextFromRSTNode(node, {
recurse(item) {
return getHTMLFromHostNodes(item, adapter);
},
handleHostNodes(item) {
const nodes = [].concat(adapter.nodeToHostNode(item, true));
return nodes.map(getHTMLFromHostNode).join('');
},
nullRenderReturnsNull: true,
});
}