Skip to content

Commit

Permalink
Fixed enzyme-related serialization tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Nov 12, 2021
1 parent 77888db commit 8c3d765
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 247 deletions.
63 changes: 59 additions & 4 deletions packages/jest/src/create-enzyme-serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,63 @@ import type { Options } from './create-serializer'
import { createSerializer as createEmotionSerializer } from './create-serializer'
import * as enzymeTickler from './enzyme-tickler'
import { createSerializer as createEnzymeToJsonSerializer } from 'enzyme-to-json'
import { isEmotionCssPropElementType, isStyledElementType } from './utils'

const enzymeSerializer = createEnzymeToJsonSerializer({})
const enzymeToJsonSerializer = createEnzymeToJsonSerializer({
map: json => {
if (typeof json.node.type === 'string') {
return json
}
const isRealStyled = json.node.type.__emotion_real === json.node.type
if (isRealStyled) {
return {
...json,
children: json.children.slice(-1)
}
}
return json
}
})

// this is a hack, leveraging the internal/implementation knowledge about the enzyme's ShallowWrapper
// there is no sane way to get this information otherwise though
const getUnrenderedElement = shallowWrapper => {
const symbols = Object.getOwnPropertySymbols(shallowWrapper)
const elementValues = symbols.filter(sym => {
const val = shallowWrapper[sym]
return !!val && val.$$typeof === Symbol.for('react.element')
})
if (elementValues.length !== 1) {
throw new Error(
"Could not get unrendered element reliably from the Enzyme's ShallowWrapper. This is a bug in Emotion - please open an issue with repro steps included:\n" +
'https://github.com/emotion-js/emotion/issues/new?assignees=&labels=bug%2C+needs+triage&template=--bug-report.md&title='
)
}
return shallowWrapper[elementValues[0]]
}

const wrappedEnzymeSerializer = {
test: enzymeToJsonSerializer.test,
print: (enzymeWrapper, printer) => {
const isShallow = !!enzymeWrapper.dive

if (isShallow) {
const unrendered = getUnrenderedElement(enzymeWrapper)
if (
(isEmotionCssPropElementType(unrendered) ||
isStyledElementType(unrendered)) &&
enzymeWrapper.getElement().type === Symbol.for('react.fragment')
) {
return enzymeToJsonSerializer.print(
enzymeWrapper.children().last(),
printer
)
}
}

return enzymeToJsonSerializer.print(enzymeWrapper, printer)
}
}

export function createEnzymeSerializer({
classNameReplacer,
Expand All @@ -16,7 +71,7 @@ export function createEnzymeSerializer({
})
return {
test(node: *) {
return enzymeSerializer.test(node) || emotionSerializer.test(node)
return wrappedEnzymeSerializer.test(node) || emotionSerializer.test(node)
},
serialize(
node: *,
Expand All @@ -26,9 +81,9 @@ export function createEnzymeSerializer({
refs: *,
printer: Function
) {
if (enzymeSerializer.test(node)) {
if (wrappedEnzymeSerializer.test(node)) {
const tickled = enzymeTickler.tickle(node)
return enzymeSerializer.print(
return wrappedEnzymeSerializer.print(
tickled,
// https://github.com/facebook/jest/blob/470ef2d29c576d6a10de344ec25d5a855f02d519/packages/pretty-format/src/index.ts#L281
valChild => printer(valChild, config, indentation, depth, refs)
Expand Down
69 changes: 34 additions & 35 deletions packages/jest/src/create-serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,46 +115,45 @@ function isShallowEnzymeElement(
})
}

const createConvertEmotionElements =
(keys: string[], printer: *) => (node: any) => {
if (isPrimitive(node)) {
return node
}
if (isEmotionCssPropEnzymeElement(node)) {
const className = enzymeTickler.getTickledClassName(node.props.css)
const labels = getLabelsFromClassName(keys, className || '')

if (isShallowEnzymeElement(node, keys, labels)) {
const emotionType = node.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__
// emotionType will be a string for DOM elements
const type =
typeof emotionType === 'string'
? emotionType
: emotionType.displayName || emotionType.name || 'Component'
return {
...node,
props: filterEmotionProps({
...node.props,
className
}),
type
}
} else {
return node.children[0]
}
}
if (isEmotionCssPropElementType(node)) {
const createConvertEmotionElements = (keys: string[]) => (node: any) => {
if (isPrimitive(node)) {
return node
}
if (isEmotionCssPropEnzymeElement(node)) {
const className = enzymeTickler.getTickledClassName(node.props.css)
const labels = getLabelsFromClassName(keys, className || '')

if (isShallowEnzymeElement(node, keys, labels)) {
const emotionType = node.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__
// emotionType will be a string for DOM elements
const type =
typeof emotionType === 'string'
? emotionType
: emotionType.displayName || emotionType.name || 'Component'
return {
...node,
props: filterEmotionProps(node.props),
type: node.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__
props: filterEmotionProps({
...node.props,
className
}),
type
}
} else {
return node.children[node.children.length - 1]
}
if (isReactElement(node)) {
return copyProps({}, node)
}
if (isEmotionCssPropElementType(node)) {
return {
...node,
props: filterEmotionProps(node.props),
type: node.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__
}
return node
}
if (isReactElement(node)) {
return copyProps({}, node)
}
return node
}

function clean(node: any, classNames: string[]) {
if (Array.isArray(node)) {
Expand Down Expand Up @@ -199,7 +198,7 @@ export function createSerializer({
) {
const elements = getStyleElements()
const keys = getKeys(elements)
const convertEmotionElements = createConvertEmotionElements(keys, printer)
const convertEmotionElements = createConvertEmotionElements(keys)
const converted = deepTransform(val, convertEmotionElements)
const nodes = getNodes(converted)
const classNames = getClassNamesFromNodes(nodes)
Expand Down
8 changes: 6 additions & 2 deletions packages/jest/src/enzyme-tickler.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ export const tickle = wrapper => {
return
}

const wrapped = (isShallow ? el.dive() : el.children()).first()
tickledCssProps.set(cssProp, wrapped.props().className)
const rendered = (isShallow ? el.dive() : el.children()).last()
const unwrapped =
rendered.type() === Symbol.for('react.fragment')
? rendered.children().last()
: rendered
tickledCssProps.set(cssProp, unwrapped.props().className)
})
return wrapper
}
16 changes: 9 additions & 7 deletions packages/jest/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,8 @@ function getClassNameProp(node) {
}

function unwrapFromPotentialFragment(node) {
// this symbol is rather stable and won't change, it is how jest was handling this initially:
// https://github.com/facebook/jest/blob/b0d28888154aec2e05cfb3249520b9a2a1a7c12d/packages/pretty-format/src/plugins/react_element.js#L20
// in environments without Symbols this would fail (React fallbacks to integers then for the element types) but we don't support such environments
// since then jest has started using `react-is` package and that could be used here too
if (node.type() === Symbol.for('react.fragment')) {
// the rendered element always comes second as the first slot is reserved for the potential style element
return node.childAt(1)
return node.children().last()
}
return node
}
Expand Down Expand Up @@ -99,11 +94,18 @@ export function isReactElement(val: any): boolean {
export function isEmotionCssPropElementType(val: any): boolean {
return (
val.$$typeof === Symbol.for('react.element') &&
val.type.$$typeof === Symbol.for('react.forward_ref') &&
val.type.displayName === 'EmotionCssPropInternal'
)
}

export function isStyledElementType(val: any): boolean {
if (val.$$typeof !== Symbol.for('react.element')) {
return false
}
const { type } = val
return type.__emotion_real === type
}

export function isEmotionCssPropEnzymeElement(val: any): boolean {
return (
val.$$typeof === Symbol.for('react.test.json') &&
Expand Down
Loading

0 comments on commit 8c3d765

Please sign in to comment.