UNPKG

kitchensink

Version:

Dispatch's awesome components and style guide

301 lines (267 loc) 8.28 kB
import isEmpty from 'lodash/isEmpty'; import isSubset from 'is-subset'; import { internalInstance, coercePropValue, nodeEqual, propsOfNode, isFunctionalComponent, splitSelector, selectorType, isCompoundSelector, AND, SELECTOR, nodeHasType, } from './Utils'; import { isDOMComponent, isCompositeComponent, isCompositeComponentWithType, isElement, findDOMNode, } from './react-compat'; import { REACT013 } from './version'; export function getNode(inst) { if (!inst || inst._store || typeof inst === 'string') { return inst; } if (inst._currentElement) { return inst._currentElement; } if (internalInstance(inst)) { return internalInstance(inst)._currentElement; } if (inst._reactInternalInstance) { return inst._reactInternalInstance._currentElement; } if (inst._reactInternalComponent) { return inst._reactInternalComponent._currentElement; } return inst; } export function instEqual(a, b, lenComp) { return nodeEqual(getNode(a), getNode(b), lenComp); } export function instHasClassName(inst, className) { if (!isDOMComponent(inst)) { return false; } const node = findDOMNode(inst); if (node.classList) { return node.classList.contains(className); } let classes = node.className || ''; if (typeof classes === 'object') { classes = classes.baseVal; } classes = classes.replace(/\s/g, ' '); return ` ${classes} `.indexOf(` ${className} `) > -1; } export function instHasId(inst, id) { if (!isDOMComponent(inst)) return false; const instId = findDOMNode(inst).id || ''; return instId === id; } function isFunctionalComponentWithType(inst, func) { return isFunctionalComponent(inst) && getNode(inst).type === func; } export function instHasType(inst, type) { switch (typeof type) { case 'string': return nodeHasType(getNode(inst), type); case 'function': return isCompositeComponentWithType(inst, type) || isFunctionalComponentWithType(inst, type); default: return false; } } export function instHasProperty(inst, propKey, stringifiedPropValue) { if (!isDOMComponent(inst)) return false; const node = getNode(inst); const nodeProps = propsOfNode(node); const descriptor = Object.getOwnPropertyDescriptor(nodeProps, propKey); if (descriptor && descriptor.get) { return false; } const nodePropValue = nodeProps[propKey]; const propValue = coercePropValue(propKey, stringifiedPropValue); // intentionally not matching node props that are undefined if (nodePropValue === undefined) { return false; } if (propValue) { return nodePropValue === propValue; } return nodeProps.hasOwnProperty(propKey); } // called with private inst export function renderedChildrenOfInst(inst) { return REACT013 ? inst._renderedComponent._renderedChildren : inst._renderedChildren; } // called with a private instance export function childrenOfInstInternal(inst) { if (!inst) { return []; } if (!inst.getPublicInstance) { const internal = internalInstance(inst); return childrenOfInstInternal(internal); } const publicInst = inst.getPublicInstance(); const currentElement = inst._currentElement; if (isDOMComponent(publicInst)) { const renderedChildren = renderedChildrenOfInst(inst); return Object.keys(renderedChildren || {}).filter((key) => { if (REACT013 && !renderedChildren[key].getPublicInstance) { return false; } return true; }).map(key => { if (!REACT013 && typeof renderedChildren[key]._currentElement.type === 'function') { return renderedChildren[key]._instance; } return renderedChildren[key].getPublicInstance(); }); } else if ( !REACT013 && isElement(currentElement) && typeof currentElement.type === 'function' ) { return childrenOfInstInternal(inst._renderedComponent); } else if ( REACT013 && isCompositeComponent(publicInst) ) { return childrenOfInstInternal(inst._renderedComponent); } return []; } export function internalInstanceOrComponent(node) { if (REACT013) { return node; } else if (node._reactInternalComponent) { return node._reactInternalComponent; } else if (node._reactInternalInstance) { return node._reactInternalInstance; } return node; } export function childrenOfInst(node) { return childrenOfInstInternal(internalInstanceOrComponent(node)); } export function pathToNode(node, root) { const queue = [root]; const path = []; while (queue.length) { const current = queue.pop(); const children = childrenOfInst(current); if (current === node) return path; 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.apply(queue, children); } return null; } export function parentsOfInst(inst, root) { return pathToNode(inst, root).reverse(); } export function instMatchesObjectProps(inst, props) { if (!isDOMComponent(inst)) return false; const node = getNode(inst); return isSubset(propsOfNode(node), props); } export function buildInstPredicate(selector) { switch (typeof selector) { case 'function': // selector is a component constructor return inst => instHasType(inst, selector); case 'string': if (isCompoundSelector.test(selector)) { return AND(splitSelector(selector).map(buildInstPredicate)); } switch (selectorType(selector)) { case SELECTOR.CLASS_TYPE: return inst => instHasClassName(inst, selector.substr(1)); case SELECTOR.ID_TYPE: return inst => instHasId(inst, selector.substr(1)); case SELECTOR.PROP_TYPE: { const propKey = selector.split(/\[([a-zA-Z][a-zA-Z_\d\-:]*?)(=|\])/)[1]; const propValue = selector.split(/=(.*?)]/)[1]; return node => instHasProperty(node, propKey, propValue); } default: // selector is a string. match to DOM tag or constructor displayName return inst => instHasType(inst, selector); } case 'object': if (!Array.isArray(selector) && selector !== null && !isEmpty(selector)) { return node => instMatchesObjectProps(node, selector); } throw new TypeError( 'Enzyme::Selector does not support an array, null, or empty object as a selector' ); default: throw new TypeError('Enzyme::Selector expects a string, object, or Component Constructor'); } } // This function should be called with an "internal instance". Nevertheless, if it is // called with a "public instance" instead, the function will call itself with the // internal instance and return the proper result. function findAllInRenderedTreeInternal(inst, test) { if (!inst) { return []; } if (!inst.getPublicInstance) { const internal = internalInstance(inst); return findAllInRenderedTreeInternal(internal, test); } const publicInst = inst.getPublicInstance() || inst._instance; let ret = test(publicInst) ? [publicInst] : []; const currentElement = inst._currentElement; if (isDOMComponent(publicInst)) { const renderedChildren = renderedChildrenOfInst(inst); Object.keys(renderedChildren || {}).filter((key) => { if (REACT013 && !renderedChildren[key].getPublicInstance) { return false; } return true; }).forEach((key) => { ret = ret.concat( findAllInRenderedTreeInternal(renderedChildren[key], test) ); }); } else if ( !REACT013 && isElement(currentElement) && typeof currentElement.type === 'function' ) { ret = ret.concat( findAllInRenderedTreeInternal( inst._renderedComponent, test ) ); } else if ( REACT013 && isCompositeComponent(publicInst) ) { ret = ret.concat( findAllInRenderedTreeInternal( inst._renderedComponent, test ) ); } return ret; } // This function could be called with a number of different things technically, so we need to // pass the *right* thing to our internal helper. export function treeFilter(node, test) { return findAllInRenderedTreeInternal(internalInstanceOrComponent(node), test); }