From 704a600a80f06a6b4f5cafb3f741e7601d393b09 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Sat, 24 Mar 2018 13:45:34 -0400 Subject: [PATCH] Add support for new types to debug() --- .../test/ShallowWrapper-spec.jsx | 32 ++++++++++++++- packages/enzyme/src/Debug.js | 35 +++++++++++++++-- packages/enzyme/src/Utils.js | 39 +++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx index bc03bc906..3803bf943 100644 --- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx @@ -6,7 +6,7 @@ import { shallow, render, ShallowWrapper, mount } from 'enzyme'; import { ITERATOR_SYMBOL, withSetStateAllowed, sym } from 'enzyme/build/Utils'; import './_helpers/setupAdapters'; -import { createClass, createContext, forwardRef } from './_helpers/react-compat'; +import { createClass, createContext, forwardRef, createPortal } from './_helpers/react-compat'; import { describeIf, itIf, itWithData, generateEmptyRenderData } from './_helpers'; import { REACT013, REACT014, REACT15, REACT150_4, REACT16, REACT163, is } from './_helpers/version'; @@ -2957,6 +2957,36 @@ describe('shallow', () => { it('should pass through to the debugNodes function', () => { expect(shallow(
).debug()).to.equal('
'); }); + + itIf(REACT163, 'should handle internal types gracefully', () => { + const { Provider, Consumer } = createContext(null); + // eslint-disable-next-line prefer-arrow-callback + const Forwarded = forwardRef(function MyComponent(props) { + return ( + + +
+ + {() =>
} + {createPortal(, { nodeType: 1 })} + +
+ + ); + }); + + expect(shallow().debug()).to.equal(` + +
+ + + [function child] + + + +
+
`); + }); }); describe('.html()', () => { diff --git a/packages/enzyme/src/Debug.js b/packages/enzyme/src/Debug.js index 27ccb372a..5cc23f400 100644 --- a/packages/enzyme/src/Debug.js +++ b/packages/enzyme/src/Debug.js @@ -12,13 +12,41 @@ import { propsOfNode, childrenOfNode, } from './RSTTraversal'; +import { + typeOf, + AsyncMode, + ContextProvider, + ContextConsumer, + Element, + ForwardRef, + Fragment, + Portal, + StrictMode, +} from './Utils'; const booleanValue = Function.bind.call(Function.call, Boolean.prototype.valueOf); + export function typeName(node) { - return typeof node.type === 'function' - ? (node.type.displayName || functionName(node.type) || 'Component') - : node.type; + const { type } = node; + switch (typeOf(node)) { + case AsyncMode: return 'AsyncMode'; + case ContextProvider: return 'ContextProvider'; + case ContextConsumer: return 'ContextConsumer'; + case Portal: return 'Portal'; + case StrictMode: return 'StrictMode'; + case ForwardRef: { + const name = type.render.displayName || functionName(type.render); + return name ? `ForwardRef(${name})` : 'ForwardRef'; + } + case Fragment: + return 'Fragment'; + case Element: + default: + return typeof node.type === 'function' + ? (type.displayName || functionName(type) || 'Component') + : type || 'unknown'; + } } export function spaces(n) { @@ -66,6 +94,7 @@ function indentChildren(childrenStrs, indentLength) { export function debugNode(node, indentLength = 2, options = {}) { if (typeof node === 'string' || typeof node === 'number') return escape(node); + if (typeof node === 'function') return '[function child]'; if (!node) return ''; const childrenStrs = compact(childrenOfNode(node).map(n => debugNode(n, indentLength, options))); diff --git a/packages/enzyme/src/Utils.js b/packages/enzyme/src/Utils.js index c479f6313..c9da05f63 100644 --- a/packages/enzyme/src/Utils.js +++ b/packages/enzyme/src/Utils.js @@ -7,8 +7,47 @@ import configuration from './configuration'; import validateAdapter from './validateAdapter'; import { childrenOfNode } from './RSTTraversal'; +const hasSymbol = typeof Symbol === 'function' && Symbol.for; + export const ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; +// TODO use react-is when a version is released with no peer dependency on React +export const AsyncMode = hasSymbol ? Symbol.for('react.async_mode') : 0xeacf; +export const ContextProvider = hasSymbol ? Symbol.for('react.provider') : 0xeacd; +export const ContextConsumer = hasSymbol ? Symbol.for('react.context') : 0xeace; +export const Element = hasSymbol ? Symbol.for('react.element') : 0xeac7; +export const ForwardRef = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0; +export const Fragment = hasSymbol ? Symbol.for('react.fragment') : 0xeacb; +export const Portal = hasSymbol ? Symbol.for('react.portal') : 0xeaca; +export const StrictMode = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc; + +export function typeOf(node) { + if (node !== null) { + const { type, $$typeof } = node; + + switch (type || $$typeof) { + case AsyncMode: + case Fragment: + case StrictMode: + return type; + default: { + const $$typeofType = type && type.$$typeof; + + switch ($$typeofType) { + case ContextConsumer: + case ForwardRef: + case Portal: + case ContextProvider: + return $$typeofType; + default: + return type || $$typeof; + } + } + } + } + return undefined; +} + export function getAdapter(options = {}) { if (options.adapter) { validateAdapter(options.adapter);