From 3870445b7e86562a7dcfa5f8d6c11b52433838c3 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Tue, 17 Oct 2017 16:18:07 -0700 Subject: [PATCH] [add] jest snapshot serializer Flatten style objects in snapshots --- .travis.yml | 4 +- docs/guides/getting-started.md | 14 +- .../__snapshots__/serializer-test.js.snap | 299 ++++++++++++++++++ jest/__tests__/serializer-test.js | 95 ++++++ jest/serializer.js | 61 ++++ package.json | 7 +- 6 files changed, 473 insertions(+), 7 deletions(-) create mode 100644 jest/__tests__/__snapshots__/serializer-test.js.snap create mode 100644 jest/__tests__/serializer-test.js create mode 100644 jest/serializer.js diff --git a/.travis.yml b/.travis.yml index 1cfe8da9..f13482f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,5 +5,5 @@ before_script: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start script: - - npm run lint - - npm test + - yarn lint + - yarn test:ci diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index 16aa6ceb..9f7c8644 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -198,10 +198,20 @@ target platform. ## Testing with Jest -[Jest](https://facebook.github.io/jest/) also needs to map `react-native` to `react-native-web`. +[Jest](https://facebook.github.io/jest/) can be configured to improve snapshots +of `react-native-web` components. ``` -"jest": { +{ + "snapshotSerializers": [ "enzyme-to-json/serializer", "react-native-web/jest/serializer" ] +} +``` + +Jest also needs to map `react-native` to `react-native-web` (unless you are +using Babel with the `react-native-web/babel` plugin). + +``` +{ "moduleNameMapper": { "react-native": "/node_modules/react-native-web" } diff --git a/jest/__tests__/__snapshots__/serializer-test.js.snap b/jest/__tests__/__snapshots__/serializer-test.js.snap new file mode 100644 index 00000000..3ae7f8fb --- /dev/null +++ b/jest/__tests__/__snapshots__/serializer-test.js.snap @@ -0,0 +1,299 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`enzyme.mount complex 1`] = ` + + + + Nested + + + } +> + +
+ + <Text + style={ + Object { + "color": "black", + "fontSize": 16, + "textAlignVertical": "center", + } + } + > + <div + className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-1bodaif rn-display-1471scf rn-font-1lw9tu2 rn-fontFamily-10u92zi rn-fontSize-ubezar rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-verticalAlign-9iso6 rn-textDecoration-bauka4 rn-whiteSpace-q42fyq rn-wordWrap-qvutc0" + dir="auto" + > + Hello World + </div> + </Text> + + +
+ +
+ + +
+ Nested +
+
+
+
+
+
+ +`; + +exports[`enzyme.mount composite 1`] = ` + + +
+ + +`; + +exports[`enzyme.mount nested 1`] = ` + + +
+ + <Text + style={ + Object { + "color": "black", + "fontSize": 16, + "textAlignVertical": "center", + } + } + > + <div + className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-1bodaif rn-display-1471scf rn-font-1lw9tu2 rn-fontFamily-10u92zi rn-fontSize-ubezar rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-verticalAlign-9iso6 rn-textDecoration-bauka4 rn-whiteSpace-q42fyq rn-wordWrap-qvutc0" + dir="auto" + > + Hello World + </div> + </Text> + +
+
+
+`; + +exports[`enzyme.mount noop 1`] = ` + +
+ +`; + +exports[`enzyme.render complex 1`] = ` +
+
+ Hello World +
+
+
+
+ Nested +
+
+
+`; + +exports[`enzyme.render composite 1`] = ` +
+`; + +exports[`enzyme.render nested 1`] = ` +
+
+ Hello World +
+
+`; + +exports[`enzyme.render noop 1`] = ` +
+`; + +exports[`enzyme.shallow complex 1`] = ` + + + Hello World + + + + + Nested + + + +`; + +exports[`enzyme.shallow composite 1`] = ` + +`; + +exports[`enzyme.shallow nested 1`] = ` + + + Hello World + + +`; + +exports[`enzyme.shallow noop 1`] = ` +
+`; + +exports[`react-test-renderer complex 1`] = ` +
+
+ Hello World +
+
+
+
+ Nested +
+
+
+`; + +exports[`react-test-renderer composite 1`] = ` +
+`; + +exports[`react-test-renderer nested 1`] = ` +
+
+ Hello World +
+
+`; + +exports[`react-test-renderer noop 1`] = ` +
+`; diff --git a/jest/__tests__/serializer-test.js b/jest/__tests__/serializer-test.js new file mode 100644 index 00000000..ef976fee --- /dev/null +++ b/jest/__tests__/serializer-test.js @@ -0,0 +1,95 @@ +/* eslint-env jasmine, jest */ +/* eslint-disable react/prop-types */ + +import { mount, render, shallow } from 'enzyme'; +import React from 'react'; +import renderer from 'react-test-renderer'; +import serializer from '../serializer'; +import { StyleSheet, Text, View } from '../../dist'; +import toJson from 'enzyme-to-json'; + +expect.addSnapshotSerializer(serializer); + +/** + * Fixtures + */ + +const Box = ({ children, element, style, ...rest }) => ( + + {children} + {element} + +); + +const Title = ({ style, ...rest }) => ; + +const styles = StyleSheet.create({ + box: { + backgroundColor: 'red', + padding: 10 + }, + boxExtra: { + alignItems: 'center' + }, + title: { + color: 'black', + fontSize: 16, + textAlignVertical: 'center' + }, + element: { + padding: 20 + } +}); + +/** + * Test cases + */ + +const cases = { + noop: , + composite: , + nested: ( + + Hello World + + ), + complex: ( + + + Nested + + } + > + Hello World + + ) +}; + +const caseNames = Object.keys(cases); + +describe('enzyme', () => { + caseNames.forEach(caseName => { + test(caseName, () => { + const element = cases[caseName]; + const mountTree = mount(element); + const renderTree = render(element); + const shallowTree = shallow(element); + + expect(toJson(mountTree)).toMatchSnapshot(`enzyme.mount ${caseName}`); + expect(toJson(renderTree)).toMatchSnapshot(`enzyme.render ${caseName}`); + expect(toJson(shallowTree)).toMatchSnapshot(`enzyme.shallow ${caseName}`); + }); + }); +}); + +describe('react-test-renderer', () => { + caseNames.forEach(caseName => { + test(caseName, () => { + const element = cases[caseName]; + const tree = renderer.create(element).toJSON(); + expect(tree).toMatchSnapshot(); + }); + }); +}); diff --git a/jest/serializer.js b/jest/serializer.js new file mode 100644 index 00000000..12461834 --- /dev/null +++ b/jest/serializer.js @@ -0,0 +1,61 @@ +const React = require('react'); +const { StyleSheet } = require('../dist'); + +function createSerializer(styleSheet) { + function flattenNodeStyles(node) { + if (node && node.props) { + // check for React elements in any props + const nextProps = Object.keys(node.props).reduce((acc, curr) => { + const value = node.props[curr]; + if (React.isValidElement(value)) { + acc[curr] = flattenNodeStyles(value); + } + return acc; + }, {}); + + // flatten styles and avoid empty objects in snapshots + if (node.props.style) { + const style = styleSheet.flatten(node.props.style); + if (Object.keys(style).length > 0) { + nextProps.style = style; + } else { + delete nextProps.style; + } + } + + const args = [node, nextProps]; + + // recurse over children too + const children = node.children || node.props.children; + if (children) { + if (Array.isArray(children)) { + children.forEach(child => { + args.push(flattenNodeStyles(child)); + }); + } else { + args.push(flattenNodeStyles(children)); + } + } + + return React.cloneElement.apply(React.cloneElement, args); + } + + return node; + } + + function test(value) { + return !!value && value.$$typeof === Symbol.for('react.test.json'); + } + + function print(value, serializer) { + return serializer(flattenNodeStyles(value)); + } + + return { test, print }; +} + +const serializer = createSerializer(StyleSheet); +createSerializer.test = serializer.test; +createSerializer.print = serializer.print; + +module.exports = createSerializer; diff --git a/package.json b/package.json index 843eb9e6..c908bc86 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,16 @@ "docs:start": "cd docs && yarn && yarn start", "docs:release": "cd docs && yarn release", "flow": "flow", - "fmt": "find babel benchmarks docs src -name '*.js' | grep -v -E '(node_modules|dist)' | xargs yarn fmt:cmd", + "fmt": "find babel benchmarks docs jest src -name '*.js' | grep -v -E '(node_modules|dist)' | xargs yarn fmt:cmd", "fmt:cmd": "prettier --print-width=100 --single-quote --write", "jest": "jest", "jest:watch": "yarn test -- --watch", - "lint": "yarn lint:cmd -- babel benchmarks docs src", + "lint": "yarn lint:cmd -- babel benchmarks docs jest src", "lint:cmd": "eslint --ignore-path .gitignore --fix", "precommit": "lint-staged", "release": "yarn clean-dist && yarn lint && yarn test && yarn build && npm publish", - "test": "flow && jest" + "test": "flow && jest", + "test:ci": "yarn clean-dist && yarn compile && yarn test" }, "babel": { "presets": [