From 858c6e70e2aa83d159dba00af16f1e34a6d93fd0 Mon Sep 17 00:00:00 2001 From: ajs139 Date: Fri, 29 Nov 2019 15:06:22 -0500 Subject: [PATCH 1/2] Improve support for Enzyme's shallow rendering (#1648) * Support shallow rendering * Clean-up and add comments * Fix flow errors * Use jest-in-case * Refactoring tests * Remove empty test remnant * refactor enzyme tests structure * Split enzyme+matchers test * Tweak changeset --- .changeset/famous-peaches-travel.md | 5 + packages/jest-emotion/src/index.js | 43 +++- packages/jest-emotion/src/utils.js | 25 +- .../__snapshots__/react-enzyme.test.js.snap | 125 ++++++++-- packages/jest-emotion/test/matchers.test.js | 51 +++- .../jest-emotion/test/react-enzyme.test.js | 229 +++++++++--------- 6 files changed, 317 insertions(+), 161 deletions(-) create mode 100644 .changeset/famous-peaches-travel.md diff --git a/.changeset/famous-peaches-travel.md b/.changeset/famous-peaches-travel.md new file mode 100644 index 000000000..5b1788b4e --- /dev/null +++ b/.changeset/famous-peaches-travel.md @@ -0,0 +1,5 @@ +--- +'jest-emotion': patch +--- + +Improve support for Enzyme's shallow rendering. diff --git a/packages/jest-emotion/src/index.js b/packages/jest-emotion/src/index.js index cbfaefa98..13b739bf1 100644 --- a/packages/jest-emotion/src/index.js +++ b/packages/jest-emotion/src/index.js @@ -9,7 +9,8 @@ import { isDOMElement, getStylesFromClassNames, getStyleElements, - getKeys + getKeys, + flatMap } from './utils' export { matchers } from './matchers' @@ -69,14 +70,50 @@ function filterEmotionProps(props = {}) { return rest } +function hasIntersection(left: any[], right: any[]) { + return left.some(value => right.includes(value)) +} + +function isShallowEnzymeElement(element, classNames) { + const delimiter = ' ' + let childClassNames = flatMap(element.children || [], ({ props = {} }) => + (props.className || '').split(delimiter) + ).filter(Boolean) + + return !hasIntersection(classNames, childClassNames) +} + export function createSerializer({ classNameReplacer, DOMElements = true }: Options = {}) { let cache = new WeakSet() function print(val: *, printer: Function) { + let elements = getStyleElements() + let keys = getKeys(elements) if (isEmotionCssPropEnzymeElement(val)) { - return val.children.map(printer).join('\n') + let cssClassNames = (val.props.css.name || '').split(' ') + let 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(val, expectedClassNames)) { + let className = [val.props.className] + .concat(expectedClassNames) + .filter(Boolean) + .join(' ') + return printer({ + ...val, + props: filterEmotionProps({ + ...val.props, + className + }), + type: val.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__ + }) + } else { + return val.children.map(printer).join('\n') + } } if (isEmotionCssPropElementType(val)) { return printer({ @@ -87,12 +124,10 @@ export function createSerializer({ } 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, diff --git a/packages/jest-emotion/src/utils.js b/packages/jest-emotion/src/utils.js index 37ec2be08..374c10f79 100644 --- a/packages/jest-emotion/src/utils.js +++ b/packages/jest-emotion/src/utils.js @@ -1,6 +1,6 @@ // @flow -function flatMap(arr, iteratee) { +export function flatMap(arr: T[], iteratee: (arg: T) => S[] | S): S[] { return [].concat(...arr.map(iteratee)) } @@ -25,14 +25,23 @@ function isTagWithClassName(node) { return node.prop('className') && typeof node.type() === 'string' } -function getClassNamesFromEnzyme(selectors, node) { - // We need to dive if we have selected a styled child from a shallow render - const actualComponent = shouldDive(node) ? node.dive() : node +function findNodeWithClassName(node) { // Find the first node with a className prop - const components = actualComponent.findWhere(isTagWithClassName) - const classes = components.length && components.first().prop('className') + const found = node.findWhere(isTagWithClassName) + return found.length ? found.first() : null +} - return getClassNames(selectors, classes) +function getClassNameProp(node) { + return (node && node.prop('className')) || '' +} + +function getClassNamesFromEnzyme(selectors, node) { + // We need to dive in to get the className if we have a styled element from a shallow render + let isShallow = shouldDive(node) + let nodeWithClassName = findNodeWithClassName( + isShallow ? node.dive().dive() : node + ) + return getClassNames(selectors, getClassNameProp(nodeWithClassName)) } function getClassNamesFromCheerio(selectors, node) { @@ -133,7 +142,7 @@ export function getStylesFromClassNames( let keyframes = {} let styles = '' - flatMap(elements, getElementRules).forEach(rule => { + flatMap(elements, getElementRules).forEach((rule: string) => { if (selectorPattern.test(rule)) { styles += rule } diff --git a/packages/jest-emotion/test/__snapshots__/react-enzyme.test.js.snap b/packages/jest-emotion/test/__snapshots__/react-enzyme.test.js.snap index bedfd8a7c..11522fb4e 100644 --- a/packages/jest-emotion/test/__snapshots__/react-enzyme.test.js.snap +++ b/packages/jest-emotion/test/__snapshots__/react-enzyme.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`enzyme mount test 1`] = ` +exports[`enzyme mount basic 1`] = ` .emotion-0 { background-color: red; } @@ -18,7 +18,7 @@ exports[`enzyme mount test 1`] = ` `; -exports[`enzyme test with prop containing css element 1`] = ` +exports[`enzyme mount with prop containing css element 1`] = ` .emotion-0 { background-color: blue; } @@ -48,28 +48,7 @@ exports[`enzyme test with prop containing css element 1`] = ` `; -exports[`enzyme test with prop containing css element in fragment 1`] = ` -.emotion-0 { - background-color: blue; -} - -.emotion-0 { - background-color: blue; -} - -
- Array [ - "x", -
- y -
, - ] -
-`; - -exports[`enzyme test with prop containing css element not at the top level 1`] = ` +exports[`enzyme mount with prop containing css element not at the top level 1`] = ` .emotion-0 { background-color: blue; } @@ -103,7 +82,7 @@ exports[`enzyme test with prop containing css element not at the top level 1`] = `; -exports[`enzyme test with prop containing css element with other label 1`] = ` +exports[`enzyme mount with prop containing css element with other label 1`] = ` .emotion-0 { background-color: blue; } @@ -141,7 +120,7 @@ exports[`enzyme test with prop containing css element with other label 1`] = ` `; -exports[`enzyme test with prop containing css element with other props 1`] = ` +exports[`enzyme mount with prop containing css element with other props 1`] = ` .emotion-0 { background-color: blue; } @@ -172,3 +151,97 @@ exports[`enzyme test with prop containing css element with other props 1`] = ` `; + +exports[`enzyme shallow basic 1`] = ` +
+ hello +
+`; + +exports[`enzyme shallow with prop containing css element 1`] = ` +
+

+ Hello +

+ + World! +
+`; + +exports[`enzyme shallow with prop containing css element not at the top level 1`] = ` +
+ + Hello +

+ } + > + World! +
+
+`; + +exports[`enzyme shallow with prop containing css element with other label 1`] = ` + + } +> +

+ Hello +

+ + World! +
+`; + +exports[`enzyme shallow with prop containing css element with other props 1`] = ` +
+

+ Hello +

+ + World! +
+`; + +exports[`enzyme with prop containing css element in fragment 1`] = ` +.emotion-0 { + background-color: blue; +} + +.emotion-0 { + background-color: blue; +} + +
+ Array [ + "x", +
+ y +
, + ] +
+`; diff --git a/packages/jest-emotion/test/matchers.test.js b/packages/jest-emotion/test/matchers.test.js index e888fc58f..f27512be5 100644 --- a/packages/jest-emotion/test/matchers.test.js +++ b/packages/jest-emotion/test/matchers.test.js @@ -19,8 +19,6 @@ describe('toHaveStyleRule', () => { width: 100%; ` - const enzymeMethods = ['mount', 'render'] - it('matches styles on the top-most node passed in', () => { const tree = renderer .create( @@ -56,21 +54,49 @@ describe('toHaveStyleRule', () => { expect(svgNode).toHaveStyleRule('width', expect.stringMatching(/.*%$/)) }) - it('supports enzyme render methods', () => { + it('supports enzyme `mount` method', () => { const Component = () => (
) - enzymeMethods.forEach(method => { - const wrapper = enzyme[method]() - expect(wrapper).toHaveStyleRule('color', 'red') - expect(wrapper).not.toHaveStyleRule('width', '100%') - const svgNode = wrapper.find('svg') - expect(svgNode).toHaveStyleRule('width', '100%') - expect(svgNode).not.toHaveStyleRule('color', 'red') - }) + const wrapper = enzyme.mount() + expect(wrapper).toHaveStyleRule('color', 'red') + expect(wrapper).not.toHaveStyleRule('width', '100%') + const svgNode = wrapper.find('svg') + expect(svgNode).toHaveStyleRule('width', '100%') + expect(svgNode).not.toHaveStyleRule('color', 'red') + }) + + it('supports enzyme `render` method', () => { + const Component = () => ( +
+ +
+ ) + + const wrapper = enzyme.render() + expect(wrapper).toHaveStyleRule('color', 'red') + expect(wrapper).not.toHaveStyleRule('width', '100%') + const svgNode = wrapper.find('svg') + expect(svgNode).toHaveStyleRule('width', '100%') + expect(svgNode).not.toHaveStyleRule('color', 'red') + }) + + it('supports enzyme `shallow` method', () => { + const Component = () => ( +
+ +
+ ) + + const wrapper = enzyme.shallow() + expect(wrapper).toHaveStyleRule('color', 'red') + expect(wrapper).not.toHaveStyleRule('width', '100%') + const svgNode = wrapper.childAt(0) + expect(svgNode).toHaveStyleRule('width', '100%') + expect(svgNode).not.toHaveStyleRule('color', 'red') }) // i think this isn't working because of forwardRef @@ -81,8 +107,7 @@ describe('toHaveStyleRule', () => { const Svg = styled('svg')` width: 100%; ` - - enzymeMethods.forEach(method => { + ;['mount', 'render', 'shallow'].forEach(method => { const wrapper = enzyme[method](
diff --git a/packages/jest-emotion/test/react-enzyme.test.js b/packages/jest-emotion/test/react-enzyme.test.js index 07514554a..e623865cc 100644 --- a/packages/jest-emotion/test/react-enzyme.test.js +++ b/packages/jest-emotion/test/react-enzyme.test.js @@ -1,130 +1,139 @@ import 'test-utils/legacy-env' /** @jsx jsx */ +import jestInCase from 'jest-in-case' import * as enzyme from 'enzyme' import { jsx } from '@emotion/core' -import { createSerializer as createEnzymeSerializer } from 'enzyme-to-json' import { createSerializer } from 'jest-emotion' -import { toMatchSnapshot } from 'jest-snapshot' import React from 'react' - -const createEnzymeSnapshotMatcher = serializerOptions => { - const serializer = createEnzymeSerializer(serializerOptions) - const identityPrinter = v => v - - return function(val) { - return toMatchSnapshot.call(this, serializer.print(val, identityPrinter)) - } -} +import toJson from 'enzyme-to-json' expect.addSnapshotSerializer(createSerializer()) -expect.extend({ - toMatchShallowSnapshot: createEnzymeSnapshotMatcher(), - toMatchDeepSnapshot: createEnzymeSnapshotMatcher({ mode: 'deep' }) -}) - -test('enzyme mount test', () => { - const Greeting = ({ children }) => ( -
{children}
- ) - const tree = enzyme.mount(hello) - expect(tree).toMatchShallowSnapshot() -}) - -test('enzyme test with prop containing css element', () => { - const Greeting = ({ children, content }) => ( -
- {content} {children} -
- ) - - const tree = enzyme.mount( - Hello

}> - World! -
- ) - expect(tree).toMatchShallowSnapshot() -}) - -test('enzyme test with prop containing css element not at the top level', () => { - const Greeting = ({ children, content }) => ( -
- {content} {children} -
- ) +const cases = { + basic: { + render() { + const Greeting = ({ children }) => ( +
{children}
+ ) + return hello + } + }, + 'with prop containing css element': { + render() { + const Greeting = ({ children, content }) => ( +
+ {content} {children} +
+ ) + return ( + Hello

}> + World! +
+ ) + } + }, + 'with prop containing css element not at the top level': { + render() { + const Greeting = ({ children, content }) => ( +
+ {content} {children} +
+ ) - const tree = enzyme.mount( -
- - Hello -

- } - > - World! -
-
- ) - expect(tree).toMatchShallowSnapshot() -}) + return ( +
+ + Hello +

+ } + > + World! +
+
+ ) + } + }, + 'with prop containing css element with other props': { + render() { + const Greeting = ({ children, content }) => ( +
+ {content} {children} +
+ ) -test('enzyme test with prop containing css element with other props', () => { - const Greeting = ({ children, content }) => ( -
- {content} {children} -
- ) - - const tree = enzyme.mount( - - Hello -

+ return ( + + Hello +

+ } + > + World! +
+ ) + } + }, + 'with prop containing css element with other label': { + render() { + const Thing = ({ content, children }) => { + return children } - > - World! -
- ) - expect(tree).toMatchShallowSnapshot() -}) + const Greeting = ({ children, content }) => ( + }> + {content} {children} + + ) -test('enzyme test with prop containing css element with other label', () => { - const Thing = ({ content, children }) => { - return children + return ( + + Hello +

+ } + > + World! +
+ ) + } } - const Greeting = ({ children, content }) => ( - }> - {content} {children} - - ) +} - const tree = enzyme.mount( - - Hello -

- } - > - World! -
+describe('enzyme', () => { + jestInCase( + 'shallow', + ({ render }) => { + const wrapper = enzyme.shallow(render()) + expect(toJson(wrapper)).toMatchSnapshot() + }, + cases ) - expect(tree).toMatchShallowSnapshot() -}) -test('enzyme test with prop containing css element in fragment', () => { - const FragmentComponent = () => ( - - x
y
-
+ jestInCase( + 'mount', + ({ render }) => { + const wrapper = enzyme.mount(render()) + expect(toJson(wrapper)).toMatchSnapshot() + }, + cases ) - const tree = enzyme.mount( -
- -
- ) - expect(tree).toMatchDeepSnapshot() + test('with prop containing css element in fragment', () => { + const FragmentComponent = () => ( + + x
y
+
+ ) + + const wrapper = enzyme.mount( +
+ +
+ ) + + expect(toJson(wrapper, { mode: 'deep' })).toMatchSnapshot() + }) }) From 1eda9737874599120a001cb03176974125438e07 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2019 21:15:22 +0100 Subject: [PATCH 2/2] Version Packages (#1661) --- .changeset/famous-peaches-travel.md | 5 ----- packages/jest-emotion/CHANGELOG.md | 6 ++++++ packages/jest-emotion/package.json | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/famous-peaches-travel.md diff --git a/.changeset/famous-peaches-travel.md b/.changeset/famous-peaches-travel.md deleted file mode 100644 index 5b1788b4e..000000000 --- a/.changeset/famous-peaches-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'jest-emotion': patch ---- - -Improve support for Enzyme's shallow rendering. diff --git a/packages/jest-emotion/CHANGELOG.md b/packages/jest-emotion/CHANGELOG.md index 19c2acfe1..763aa1407 100644 --- a/packages/jest-emotion/CHANGELOG.md +++ b/packages/jest-emotion/CHANGELOG.md @@ -1,5 +1,11 @@ # jest-emotion +## 10.0.25 + +### Patch Changes + +- [`858c6e70`](https://github.com/emotion-js/emotion/commit/858c6e70e2aa83d159dba00af16f1e34a6d93fd0) [#1648](https://github.com/emotion-js/emotion/pull/1648) Thanks [@ajs139](https://github.com/ajs139)! - Improve support for Enzyme's shallow rendering. + ## 10.0.17 ### Patch Changes diff --git a/packages/jest-emotion/package.json b/packages/jest-emotion/package.json index 35fb6a3c1..f1d2f6b03 100644 --- a/packages/jest-emotion/package.json +++ b/packages/jest-emotion/package.json @@ -1,6 +1,6 @@ { "name": "jest-emotion", - "version": "10.0.17", + "version": "10.0.25", "description": "Jest utilities for emotion", "main": "dist/jest-emotion.cjs.js", "types": "types/index.d.ts", @@ -49,4 +49,4 @@ "browser": { "./dist/jest-emotion.cjs.js": "./dist/jest-emotion.browser.cjs.js" } -} \ No newline at end of file +}