Skip to content

Commit

Permalink
Cleanup @emotion/jest entrypoints
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Jun 27, 2020
1 parent 7d475c3 commit b53a662
Show file tree
Hide file tree
Showing 22 changed files with 309 additions and 338 deletions.
12 changes: 6 additions & 6 deletions packages/jest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ The easiest way to test React components with emotion is with the snapshot seria
module.exports = {
// ... other config
snapshotSerializers: [
'@emotion/jest' /* if needed other snapshotSerializers should go here */
'@emotion/jest/serializer' /* if needed other snapshotSerializers should go here */
]
}
```

To assist with shallow rendering, there's a custom enzyme snapshot serializer, that includes the `enzyme-to-json` serializer, which is available by importing `@emotion/jest/enzyme`. If you already have the `enzyme-to-json` serializer added to `snapshotSerializers`, it will need to be removed to allow this to work.
To assist with shallow rendering, there's a custom enzyme snapshot serializer, that includes the `enzyme-to-json` serializer, which is available by importing `@emotion/jest/enzyme-serializer`. If you already have the `enzyme-to-json` serializer added to `snapshotSerializers`, it will need to be removed to allow this to work.

```js
// jest.config.js
module.exports = {
// ... other config
snapshotSerializers: ['@emotion/jest/enzyme']
snapshotSerializers: ['@emotion/jest/enzyme-serializer']
}
```

Expand All @@ -37,10 +37,10 @@ Or you can add the serializer via the `expect.addSnapshotSerializer` method like
```jsx
import React from 'react'
import renderer from 'react-test-renderer'
import serializer from '@emotion/jest'
import { createSerializer } from '@emotion/jest'
import styled from '@emotion/styled'

expect.addSnapshotSerializer(serializer)
expect.addSnapshotSerializer(createSerializer())

test('renders with correct styles', () => {
const H1 = styled.h1`
Expand Down Expand Up @@ -141,7 +141,7 @@ test('renders with correct styles', () => {
})
```

You can provide additional options for `toHaveStyleRule` matcher.
You can provide additional options for `toHaveStyleRule` matcher.
`target` - helps to specify css selector or other component
where style rule should be found.

Expand Down
1 change: 1 addition & 0 deletions packages/jest/enzyme-serializer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../src/create-enzyme-serializer'
8 changes: 8 additions & 0 deletions packages/jest/enzyme-serializer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"main": "dist/jest.cjs.js",
"module": "dist/jest.esm.js",
"types": "../types/enzyme-serializer",
"preconstruct": {
"source": "../src/enzyme-serializer"
}
}
1 change: 0 additions & 1 deletion packages/jest/enzyme/index.js

This file was deleted.

13 changes: 0 additions & 13 deletions packages/jest/enzyme/types/index.d.ts

This file was deleted.

7 changes: 4 additions & 3 deletions packages/jest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"src",
"dist",
"types/*.d.ts",
"enzyme",
"serializer.js"
"serializer",
"enzyme-serializer"
],
"scripts": {
"test:typescript": "dtslint types"
Expand Down Expand Up @@ -61,7 +61,8 @@
"preconstruct": {
"entrypoints": [
".",
"enzyme"
"serializer",
"enzyme-serializer"
]
}
}
1 change: 0 additions & 1 deletion packages/jest/serializer.js

This file was deleted.

1 change: 1 addition & 0 deletions packages/jest/serializer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../src/create-serializer'
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"main": "dist/jest.cjs.js",
"module": "dist/jest.esm.js",
"types": "../types/enzyme",
"types": "../types/serializer",
"preconstruct": {
"source": "../src/enzyme"
"source": "../src/serializer"
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// @flow
import type { Options } from './serializer'
import { createSerializer as createEmotionSerializer } from './serializer'
import { createSerializer as createEnzymeSerializer } from 'enzyme-to-json'
import { createSerializer as createEmotionSerializer } from './create-serializer'
import { createSerializer as createEnzymeToJsonSerializer } from 'enzyme-to-json'

const enzymeSerializer = createEnzymeSerializer({})
const enzymeSerializer = createEnzymeToJsonSerializer({})

const tickle = (wrapper: *) => {
if (typeof wrapper.dive === 'function') {
Expand All @@ -12,7 +12,7 @@ const tickle = (wrapper: *) => {
return wrapper
}

export function createSerializer({
export function createEnzymeSerializer({
classNameReplacer,
DOMElements = true
}: Options = {}) {
Expand Down Expand Up @@ -52,5 +52,3 @@ export function createSerializer({
}
}
}

export default createSerializer()
236 changes: 236 additions & 0 deletions packages/jest/src/create-serializer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// @flow
import prettify from '@emotion/css-prettifier'
import { replaceClassNames } from './replace-class-names'
import {
getClassNamesFromNodes,
isReactElement,
isEmotionCssPropElementType,
isEmotionCssPropEnzymeElement,
isDOMElement,
getStylesFromClassNames,
getStyleElements,
getKeys,
flatMap,
isPrimitive,
hasIntersection
} from './utils'

function getNodes(node, nodes = []) {
if (Array.isArray(node)) {
for (let child of node) {
getNodes(child, nodes)
}
return nodes
}

if (node.children) {
for (let child of node.children) {
getNodes(child, nodes)
}
}

if (typeof node === 'object') {
nodes.push(node)
}

return nodes
}

function copyProps(src, target) {
return Object.defineProperties(src, {
...Object.getOwnPropertyDescriptors(target)
})
}

function deepTransform(node, transform) {
if (Array.isArray(node)) {
return node.map(child => deepTransform(child, transform))
}

const transformed: any = transform(node)

if (transformed !== node) {
if (transformed.props) {
copyProps(transformed, {
props: Object.entries(transformed.props).reduce(
(props, [key, value]) =>
Object.assign(props, {
[key]: deepTransform(value, transform)
}),
{}
)
})
}
if (transformed.children) {
return copyProps(transformed, {
// flatMap to allow a child of <A><B /><C /></A> to be transformed to <B /><C />
children: flatMap(
(deepTransform(transformed.children, transform): any),
id => id
)
})
}
}

return transformed
}

function getPrettyStylesFromClassNames(
classNames: Array<string>,
elements: Array<HTMLStyleElement>,
indentation: string
) {
return prettify(getStylesFromClassNames(classNames, elements), indentation)
}

export type Options = {
classNameReplacer?: (className: string, index: number) => string,
DOMElements?: boolean
}

function filterEmotionProps(props = {}) {
const {
css,
__EMOTION_TYPE_PLEASE_DO_NOT_USE__,
__EMOTION_LABEL_PLEASE_DO_NOT_USE__,
...rest
} = props

rest.css = 'unknown styles'

return rest
}

function isShallowEnzymeElement(element: any, classNames: string[]) {
const delimiter = ' '
const childClassNames = flatMap(element.children || [], ({ props = {} }) =>
(props.className || '').split(delimiter)
).filter(Boolean)
return !hasIntersection(classNames, childClassNames)
}

const createConvertEmotionElements = (keys: string[], printer: *) => (
node: any
) => {
if (isPrimitive(node)) {
return node
}
if (isEmotionCssPropEnzymeElement(node)) {
const cssClassNames = (node.props.css.name || '').split(' ')
const expectedClassNames = flatMap(cssClassNames, cssClassName =>
keys.map(key => `${key}-${cssClassName}`)
)
// if this is a shallow element, we need to manufacture the className
// since the underlying component is not rendered.
if (isShallowEnzymeElement(node, expectedClassNames)) {
const className = [node.props.className]
.concat(expectedClassNames)
.filter(Boolean)
.join(' ')
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.name
return {
...node,
props: filterEmotionProps({
...node.props,
className
}),
type
}
} else {
return node.children
}
}
if (isEmotionCssPropElementType(node)) {
return {
...node,
props: filterEmotionProps(node.props),
type: node.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__
}
}
if (isReactElement(node)) {
return copyProps({}, node)
}
return node
}

function clean(node: any, classNames: string[]) {
if (Array.isArray(node)) {
for (const child of node) {
clean(child, classNames)
}
return
}
if (node.children) {
for (const child of node.children) {
clean(child, classNames)
}
}
if (node.props) {
const { className } = node.props
if (!className) {
// if it's empty, remove it
delete node.props.className
} else {
const hasKnownClass = hasIntersection(className.split(' '), classNames)
if (hasKnownClass) {
delete node.props.css
}
}
}
}

export function createSerializer({
classNameReplacer,
DOMElements = true
}: Options = {}) {
const cache = new WeakSet()
const isTransformed = val => cache.has(val)

function serialize(
val: *,
config: *,
indentation: string,
depth: number,
refs: *,
printer: Function
) {
const elements = getStyleElements()
const keys = getKeys(elements)
const convertEmotionElements = createConvertEmotionElements(keys, printer)
const converted = deepTransform(val, convertEmotionElements)
const nodes = getNodes(converted)
const classNames = getClassNamesFromNodes(nodes)
const styles = getPrettyStylesFromClassNames(
classNames,
elements,
config.indent
)
clean(converted, classNames)

nodes.forEach(cache.add, cache)
const printedVal = printer(converted, config, indentation, depth, refs)
nodes.forEach(cache.delete, cache)

return replaceClassNames(
classNames,
styles,
printedVal,
keys,
classNameReplacer
)
}

return {
test(val: *) {
return (
val &&
(!isTransformed(val) &&
(isReactElement(val) || (DOMElements && isDOMElement(val))))
)
},
serialize
}
}
3 changes: 3 additions & 0 deletions packages/jest/src/enzyme-serializer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @flow
import { createEnzymeSerializer } from './create-enzyme-serializer'
export const { test, serialize } = createEnzymeSerializer()
3 changes: 2 additions & 1 deletion packages/jest/src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @flow
export { createSerializer, default } from './serializer'
export { createSerializer } from './create-serializer'
export { createEnzymeSerializer } from './create-enzyme-serializer'
export { matchers } from './matchers'
Loading

0 comments on commit b53a662

Please sign in to comment.