Skip to content

Commit

Permalink
Improve support for Enzyme's shallow rendering (#1672)
Browse files Browse the repository at this point in the history
* Improve support for Enzyme's shallow rendering

* Remove empty className from snapshots

* Fix flow errors

* Remove [transformed] from plain objects, add interesting test case

* Clean-up code

* don't transfrom DOM elements

* Updating interesting test case, the plot thickens

* Resolved the curious test isolatiion issue - clean JSDOM after each test

* Move enzyme-specific serialization to subpackage

* Update docs/testing.mdx

Co-Authored-By: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Update packages/jest-emotion/README.md

Co-Authored-By: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Add types to jest-emotion/enzyme

* Changes per code review

* Update packages/jest-emotion/test/react-enzyme.test.js

Co-Authored-By: Mitchell Hamilton <mitchell@hamil.town>

* Add message to remove enyzme-to-json if it's present

* Add warning if no JSOM when getting styles

* Update utils.js

* Update utils.js

* Update utils.js

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
Co-authored-by: Mitchell Hamilton <mitchell@hamil.town>
  • Loading branch information
3 people committed Jan 5, 2020
1 parent c7850e6 commit 5936812
Show file tree
Hide file tree
Showing 18 changed files with 931 additions and 327 deletions.
13 changes: 11 additions & 2 deletions docs/testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,20 @@ npm install --save-dev jest-emotion

Add the snapshot serializer in your [`setupTestFrameworkScriptFile`](http://facebook.github.io/jest/docs/en/configuration.html#setuptestframeworkscriptfile-string) _or_ at the top of your test file.

When using Enzyme, it's recommended to use the combined enzyme and emotion serializer to support shallow rendering:

```javascript
import { createSerializer } from 'jest-emotion/enzyme' // also adds the enzyme-to-json serializer

expect.addSnapshotSerializer(createSerializer())
```

Otherwise, use standard `createSerializer` method.

```javascript
import * as emotion from 'emotion'
import { createSerializer } from 'jest-emotion'

expect.addSnapshotSerializer(createSerializer(emotion))
expect.addSnapshotSerializer(createSerializer())
```

### Writing a test
Expand Down
1 change: 0 additions & 1 deletion packages/create-emotion/test/css.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @flow
import 'test-utils/legacy-env'
import 'test-utils/next-env'
import React from 'react'
import renderer from 'react-test-renderer'
Expand Down
16 changes: 13 additions & 3 deletions packages/jest-emotion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,19 @@ The easiest way to test React components with emotion is with the snapshot seria
// jest.config.js
module.exports = {
// ... other config
snapshotSerializers: [
'jest-emotion' /* if needed other snapshotSerializers should go here */
]
snapshotSerializers: ['jest-emotion']
}
```

To assist with shallow rendering, there's a custom enzyme snapshot serializer, that includes the `enzyme-to-json`
serializer, which is available by importing `jest-emotion/enzyme`. 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: ['jest-emotion/enzyme']
}
```

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

export interface CreateSerializerOptions {
classNameReplacer?: (className: string, index: number) => string
DOMElements?: boolean
}
export function createSerializer(
options?: CreateSerializerOptions
): jest.SnapshotSerializerPlugin
export const print: jest.SnapshotSerializerPlugin['print']
export const test: jest.SnapshotSerializerPlugin['test']
declare const serializer: jest.SnapshotSerializerPlugin
export default serializer
17 changes: 15 additions & 2 deletions packages/jest-emotion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
"version": "11.0.0-next.7",
"description": "Jest utilities for emotion",
"main": "dist/jest-emotion.cjs.js",
"module": "dist/jest-emotion.esm.js",
"types": "types/index.d.ts",
"files": [
"src",
"dist",
"types/*.d.ts",
"enzyme",
"serializer.js"
],
"scripts": {
Expand All @@ -20,6 +22,14 @@
"css": "^2.2.1",
"specificity": "^0.4.1"
},
"peerDependencies": {
"enzyme-to-json": "^3.2.1"
},
"peerDependenciesMeta": {
"enzyme-to-json": {
"optional": true
}
},
"devDependencies": {
"@emotion/core": "^11.0.0-next.7",
"dtslint": "^0.3.0",
Expand Down Expand Up @@ -47,7 +57,10 @@
"bugs": {
"url": "https://github.com/emotion-js/emotion/issues"
},
"browser": {
"./dist/jest-emotion.cjs.js": "./dist/jest-emotion.browser.cjs.js"
"preconstruct": {
"entrypoints": [
".",
"enzyme"
]
}
}
43 changes: 43 additions & 0 deletions packages/jest-emotion/src/enzyme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// @flow
import type { Options } from './serializer'
import { createSerializer as createEmotionSerializer } from './serializer'
import { createSerializer as createEnzymeSerializer } from 'enzyme-to-json'

const enzymeSerializer = createEnzymeSerializer({})

const tickle = (wrapper: *) => {
if (typeof wrapper.dive === 'function') {
wrapper.find('EmotionCssPropInternal').forEach(el => el.dive())
}
return wrapper
}

export function createSerializer({
classNameReplacer,
DOMElements = true
}: Options = {}) {
const emotionSerializer = createEmotionSerializer({
classNameReplacer,
DOMElements
})
return {
test(node: *) {
return enzymeSerializer.test(node) || emotionSerializer.test(node)
},
print(node: *, printer: *) {
let result = node
if (enzymeSerializer.test(node)) {
const tickled = tickle(node)
result = enzymeSerializer.print(tickled, printer)
}
if (emotionSerializer.test(node)) {
result = emotionSerializer.print(result, printer)
}
return result
}
}
}

export const { print, test } = createSerializer()

export default { print, test }
117 changes: 2 additions & 115 deletions packages/jest-emotion/src/index.js
Original file line number Diff line number Diff line change
@@ -1,119 +1,6 @@
// @flow
import * as css from 'css'
import { replaceClassNames } from './replace-class-names'
import {
getClassNamesFromNodes,
isReactElement,
isEmotionCssPropElementType,
isEmotionCssPropEnzymeElement,
isDOMElement,
getStylesFromClassNames,
getStyleElements,
getKeys
} from './utils'

import { createSerializer, print, test } from './serializer'
export { matchers } from './matchers'

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 getPrettyStylesFromClassNames(
classNames: Array<string>,
elements: Array<HTMLStyleElement>
) {
let styles = getStylesFromClassNames(classNames, elements)

let prettyStyles
try {
prettyStyles = css.stringify(css.parse(styles))
} catch (e) {
console.error(e)
throw new Error(`There was an error parsing the following css: "${styles}"`)
}
return prettyStyles
}

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
}

export function createSerializer({
classNameReplacer,
DOMElements = true
}: Options = {}) {
let cache = new WeakSet()
function print(val: *, printer: Function) {
if (isEmotionCssPropEnzymeElement(val)) {
return val.children.map(printer).join('\n')
}
if (isEmotionCssPropElementType(val)) {
return printer({
...val,
props: filterEmotionProps(val.props),
type: val.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__
})
}
const nodes = getNodes(val)
const classNames = getClassNamesFromNodes(nodes)
let elements = getStyleElements()
const styles = getPrettyStylesFromClassNames(classNames, elements)
nodes.forEach(cache.add, cache)
const printedVal = printer(val)
nodes.forEach(cache.delete, cache)
let keys = getKeys(elements)
return replaceClassNames(
classNames,
styles,
printedVal,
keys,
classNameReplacer
)
}

function test(val: *) {
return (
val &&
((!cache.has(val) &&
(isReactElement(val) || (DOMElements && isDOMElement(val)))) ||
isEmotionCssPropEnzymeElement(val) ||
isEmotionCssPropElementType(val))
)
}
return { test, print }
}

export let { print, test } = createSerializer()

export { createSerializer, print, test }
export default { print, test }
Loading

0 comments on commit 5936812

Please sign in to comment.