Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup @emotion/jest entrypoints #1920

Merged
merged 4 commits into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 type { Options } from './create-serializer'
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