diff --git a/.eslintrc b/.eslintrc index 5a7ed5d6..9be846eb 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,9 +14,7 @@ "node": true, "jest": true }, - "globals": { - "sinon": true - }, + "globals": {}, "rules": { "semi": [2, "never"], "no-use-before-define": ["error", { "functions": false }], @@ -42,7 +40,7 @@ "react/no-multi-comp": [0], "react/sort-comp": [0], "react/jsx-filename-extension": [0], - "prettier/prettier": ["error", {"semi": false, "trailingComma": "es5", "singleQuote": true}], + "prettier/prettier": "error", "react/destructuring-assignment": [0], "react/jsx-curly-brace-presence": [0], "react/no-unused-prop-types": [0], diff --git a/.prettierrc b/.prettierrc index f81ade1f..d6e96f7f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,4 @@ semi: false singleQuote: true -trailingComma: es5 \ No newline at end of file +trailingComma: es5 +parser: babel diff --git a/.size-snapshot.json b/.size-snapshot.json index f79279a7..6e0f7f5a 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,25 +1,25 @@ { - "lib/packages/recompose/dist/Recompose.umd.js": { - "bundled": 56428, - "minified": 19441, - "gzipped": 5636 + "Recompose.umd.js": { + "bundled": 49359, + "minified": 17069, + "gzipped": 5102 }, - "lib/packages/recompose/dist/Recompose.min.js": { - "bundled": 48906, - "minified": 17887, - "gzipped": 5043 + "Recompose.min.js": { + "bundled": 41591, + "minified": 15506, + "gzipped": 4511 }, - "lib/packages/recompose/dist/Recompose.esm.js": { - "bundled": 32428, - "minified": 15083, - "gzipped": 3550, + "Recompose.esm.js": { + "bundled": 32330, + "minified": 15254, + "gzipped": 3644, "treeshaked": { "rollup": { - "code": 310, - "import_statements": 310 + "code": 311, + "import_statements": 311 }, "webpack": { - "code": 1838 + "code": 1874 } } } diff --git a/README.md b/README.md index 2777f790..69703cef 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ The project is a fork of [Recompose](https://github.com/acdlite/recompose/) whic [fbjs](https://github.com/facebook/fbjs) was one of the core dependencies for the last npm release of [Recompose](https://github.com/acdlite/recompose/) i.e [![npm version](https://img.shields.io/npm/v/recompose.svg?style=flat-square)](https://www.npmjs.com/package/recompose). The [Recompose](https://github.com/acdlite/recompose/) project was updated to remove the usage of [fbjs](https://github.com/facebook/fbjs), but it was never published to npm. The fork removes `fbjs` as dependency and updates `@babel/runtime`, `react-lifecycles-compat`, `symbol-observable` and `hoist-non-react-statics` packages to latest versions. +Use `"@shakacode/recompose": "^0.31.0"` for React 17-19, `"@shakacode/recompose": "^0.30.3"` for earlier React versions. Recompose ----- diff --git a/babel.config.js b/babel.config.js index 56056cfe..27597cc9 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,5 +1,5 @@ module.exports = { - plugins: [['@babel/proposal-class-properties', { loose: true }]], + plugins: [['@babel/transform-class-properties', { loose: true }]], presets: [['@babel/env', { loose: true }], '@babel/react'], } diff --git a/docs/API.md b/docs/API.md index 3f250d8e..5c422035 100644 --- a/docs/API.md +++ b/docs/API.md @@ -550,23 +550,27 @@ const Post = enhance(({ title, content, author }) => ```ts // TContext = the type of the provided child context withContext( - childContextTypes: PropTypes.ValidationMap, + childContextTypes: Record, getChildContext: (props: Object) => TContext ): HigherOrderComponent ``` -Provides context to the component's children. `childContextTypes` is an object of React prop types. `getChildContext()` is a function that returns the child context. Use along with `getContext()`. +Provides context to the component's children. `childContextTypes` is an object whose keys define the context properties to provide. `getChildContext()` is a function that returns the child context values. Use along with `getContext()`. + +Note: this uses `React.createContext` internally. The keys of `childContextTypes` are used to create or look up context objects; the values are ignored. This means prop-types validation is no longer performed on context values. Existing code passing `PropTypes.ValidationMap` objects will continue to work. ### `getContext()` ```ts -getContext( - contextTypes: Object +getContext( + contextTypes: Record ): HigherOrderComponent ``` Gets values from context and passes them along as props. Use along with `withContext()`. +Note: like `withContext()`, the keys of `contextTypes` determine which context values to consume; the values are ignored. + ### `lifecycle()` ```ts diff --git a/package.json b/package.json index 819aff8e..bfbb6012 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "url": "git://github.com/acdlite/recompose.git" }, "license": "MIT", + "packageManager": "yarn@1.22.22", "scripts": { "lint": "eslint scripts src", "build:recompose": "cross-env PACKAGE_NAME=recompose rollup --config scripts/rollup.config.js", @@ -14,7 +15,7 @@ "test:watch": "cross-env BABEL_ENV=cjs jest --watch", "release": "node scripts/release.js", "postinstall": "node scripts/installNestedPackageDeps.js", - "format": "prettier --semi false --trailing-comma es5 --single-quote --write 'scripts/*.js' 'src/packages/*/*.js' 'src/packages/*/!(node_modules)/**/*.js'", + "format": "prettier --write 'scripts/*.js' 'src/packages/*/*.js' 'src/packages/*/!(node_modules)/**/*.js'", "precommit": "lint-staged", "prepush": "yarn test" }, @@ -26,71 +27,66 @@ "text-summary", "lcov" ], - "setupTestFrameworkScriptFile": "/scripts/jest.setup.js" + "testTimeout": 20000, + "testEnvironment": "jsdom", + "restoreMocks": true }, "lint-staged": { "*.js": [ - "prettier --semi false --trailing-comma es5 --single-quote --write", + "prettier --write", "eslint --fix", "git add" ] }, "devDependencies": { - "@babel/cli": "^7.0.0", - "@babel/core": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/preset-env": "^7.0.0", - "@babel/preset-react": "^7.0.0", - "@babel/runtime": "^7.0.0", - "babel-core": "^7.0.0-bridge.0", + "@babel/cli": "^7.22.0", + "@babel/core": "^7.22.0", + "@babel/plugin-transform-class-properties": "^7.25.0", + "@babel/plugin-transform-runtime": "^7.22.0", + "@babel/preset-env": "^7.22.0", + "@babel/preset-react": "^7.22.0", + "@babel/runtime": "^7.22.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.3.0", "babel-eslint": "^9.0.0", - "babel-jest": "^22.4.3", + "babel-jest": "^29.7.0", "baconjs": "^0.7.84", "chalk": "^1.1.1", "change-case": "^2.3.1", "codecov": "^1.0.1", "create-react-class": "^15.5.0", "cross-env": "^4.0.0", - "enzyme": "^3.3.0", "eslint": "^5.3.0", "eslint-config-airbnb": "^17.0.0", "eslint-config-prettier": "^2.9.0", "eslint-plugin-import": "^2.13.0", "eslint-plugin-jsx-a11y": "^6.1.1", - "eslint-plugin-prettier": "^2.0.1", + "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-react": "^7.10.0", "flow-bin": "^0.72.0", "flyd": "^0.2.4", "husky": "^0.13.3", - "jest": "^22.4.3", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "kefir": "^3.2.3", "lint-staged": "^3.4.0", "most": "^1.0.2", - "prettier": "^1.2.2", + "prettier": "^1.19.1", "prop-types": "^15.6.1", - "react": "^16.3.1", - "react-dom": "^16.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "readline-sync": "^1.2.21", - "rollup": "^0.65.0", - "rollup-plugin-babel": "^4.0.1", - "rollup-plugin-commonjs": "^9.1.6", - "rollup-plugin-node-resolve": "^3.3.0", - "rollup-plugin-replace": "^2.0.0", - "rollup-plugin-size-snapshot": "^0.6.1", - "rollup-plugin-uglify": "^4.0.0", + "@rollup/plugin-babel": "^6.0.0", + "@rollup/plugin-commonjs": "^28.0.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "@rollup/plugin-replace": "^6.0.0", + "@rollup/plugin-terser": "^0.4.0", + "rollup": "^4.0.0", + "rollup-plugin-size-snapshot": "^0.12.0", "rx": "^4.1.0", "rxjs": "^5.0.0", "shelljs": "^0.6.0", - "sinon": "^1.17.1", - "webpack": "^2.4.1", "xstream": "^5.0.5" }, - "devEngines": { - "node": "5.x", - "npm": "3.x" - }, - "dependencies": { - "enzyme-adapter-react-16": "^1.1.1" - } + "dependencies": {} } diff --git a/scripts/jest.setup.js b/scripts/jest.setup.js deleted file mode 100644 index 43270604..00000000 --- a/scripts/jest.setup.js +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable */ -jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000 - -import Enzyme from 'enzyme' -import Adapter from 'enzyme-adapter-react-16' - -Enzyme.configure({ adapter: new Adapter() }) diff --git a/scripts/rollup.config.js b/scripts/rollup.config.js index d5c84fca..3f3a1d80 100644 --- a/scripts/rollup.config.js +++ b/scripts/rollup.config.js @@ -1,12 +1,14 @@ import path from 'path' -import nodeResolve from 'rollup-plugin-node-resolve' -import babel from 'rollup-plugin-babel' -import replace from 'rollup-plugin-replace' -import commonjs from 'rollup-plugin-commonjs' -import { uglify } from 'rollup-plugin-uglify' +import { createRequire } from 'module' +import nodeResolve from '@rollup/plugin-node-resolve' +import babel from '@rollup/plugin-babel' +import replace from '@rollup/plugin-replace' +import commonjs from '@rollup/plugin-commonjs' +import terser from '@rollup/plugin-terser' import { sizeSnapshot } from 'rollup-plugin-size-snapshot' import { pascalCase } from 'change-case' +const require = createRequire(import.meta.url) const { PACKAGES_SRC_DIR, PACKAGES_OUT_DIR } = require('./getPackageNames') const packageName = process.env.PACKAGE_NAME @@ -21,7 +23,7 @@ const isExternal = id => !id.startsWith('.') && !id.startsWith('/') const getBabelOptions = ({ useESModules }) => ({ exclude: '**/node_modules/**', - runtimeHelpers: true, + babelHelpers: 'runtime', plugins: [['@babel/transform-runtime', { useESModules }]], }) @@ -43,7 +45,10 @@ export default [ nodeResolve(), babel(getBabelOptions({ useESModules: true })), commonjs(), - replace({ 'process.env.NODE_ENV': JSON.stringify('development') }), + replace({ + preventAssignment: true, + 'process.env.NODE_ENV': JSON.stringify('development'), + }), sizeSnapshot({ matchSnapshot }), ], }, @@ -63,9 +68,12 @@ export default [ nodeResolve(), babel(getBabelOptions({ useESModules: true })), commonjs(), - replace({ 'process.env.NODE_ENV': JSON.stringify('production') }), + replace({ + preventAssignment: true, + 'process.env.NODE_ENV': JSON.stringify('production'), + }), sizeSnapshot({ matchSnapshot }), - uglify(), + terser(), ], }, diff --git a/src/packages/recompose/__tests__/branch-test.js b/src/packages/recompose/__tests__/branch-test.js index 82e1360a..792128f7 100644 --- a/src/packages/recompose/__tests__/branch-test.js +++ b/src/packages/recompose/__tests__/branch-test.js @@ -1,6 +1,5 @@ -import sinon from 'sinon' import React from 'react' -import { mount } from 'enzyme' +import { render, fireEvent } from '@testing-library/react' import { branch, compose, withState, withProps } from '../' test('branch tests props and applies one of two HoCs, for true and false', () => { @@ -11,29 +10,24 @@ test('branch tests props and applies one of two HoCs, for true and false', () => withProps({ name: 'Heisenberg' }), withProps({ name: 'Walter' }) ) - )(({ isBad, name, updateIsBad }) => + )(({ isBad, name, updateIsBad }) => (
-
- {isBad ? 'true' : 'false'} -
-
- {name} -
+
{isBad ? 'true' : 'false'}
+
{name}
- ) + )) expect(SayMyName.displayName).toBe('withState(branch(Component))') - const wrapper = mount() - const getIsBad = () => wrapper.find('.isBad').text() - const getName = () => wrapper.find('.name').text() - const toggle = wrapper.find('button') + const { container } = render() + const getIsBad = () => container.querySelector('.isBad').textContent + const getName = () => container.querySelector('.name').textContent expect(getIsBad()).toBe('false') expect(getName()).toBe('Walter') - toggle.simulate('click') + fireEvent.click(container.querySelector('button')) expect(getIsBad()).toBe('true') expect(getName()).toBe('Heisenberg') @@ -48,23 +42,23 @@ test('branch defaults third argument to identity function', () => { () => props => )(Right) - const wrapper = mount() - const right = wrapper.find('.right').text() + const { container } = render() + const right = container.querySelector('.right').textContent expect(right).toBe('Right') }) test('branch third argument should not cause console error', () => { - const error = sinon.stub(console, 'error') + jest.spyOn(console, 'error').mockImplementation(() => {}) const Component = () =>
Component
- const BranchedComponent = branch(() => false, v => v, v => v)(Component) - - mount() + const BranchedComponent = branch( + () => false, + v => v, + v => v + )(Component) - expect(error.called).toBe(false) + render() - /* eslint-disable */ - error.restore() - /* eslint-enable */ + expect(console.error).not.toHaveBeenCalled() }) diff --git a/src/packages/recompose/__tests__/componentFromProp-test.js b/src/packages/recompose/__tests__/componentFromProp-test.js index 437ccb52..0475267d 100644 --- a/src/packages/recompose/__tests__/componentFromProp-test.js +++ b/src/packages/recompose/__tests__/componentFromProp-test.js @@ -1,17 +1,16 @@ import React from 'react' -import { mount } from 'enzyme' +import { render } from '@testing-library/react' import { componentFromProp } from '../' test('componentFromProp creates a component that takes a component as a prop and renders it with the rest of the props', () => { const Container = componentFromProp('component') expect(Container.displayName).toBe('componentFromProp(component)') - const Component = ({ pass }) => -
- Pass: {pass} -
+ const Component = ({ pass }) =>
Pass: {pass}
- const wrapper = mount() - const div = wrapper.find('div') - expect(div.text()).toBe('Pass: through') + const { container } = render( + + ) + const div = container.querySelector('div') + expect(div.textContent).toBe('Pass: through') }) diff --git a/src/packages/recompose/__tests__/componentFromStream-test.js b/src/packages/recompose/__tests__/componentFromStream-test.js index a85767f5..5e882eac 100644 --- a/src/packages/recompose/__tests__/componentFromStream-test.js +++ b/src/packages/recompose/__tests__/componentFromStream-test.js @@ -1,7 +1,6 @@ import React from 'react' -import { mount } from 'enzyme' +import { render, act } from '@testing-library/react' import { Observable, Subject } from 'rxjs' -import sinon from 'sinon' import rxjsConfig from '../rxjsObservableConfig' import { componentFromStreamWithConfig } from '../componentFromStream' @@ -9,17 +8,13 @@ const componentFromStream = componentFromStreamWithConfig(rxjsConfig) test('componentFromStream creates a component from a prop stream transformation', () => { const Double = componentFromStream(props$ => - props$.map(({ n }) => -
- {n * 2} -
- ) + props$.map(({ n }) =>
{n * 2}
) ) - const wrapper = mount() - const div = wrapper.find('div') - expect(div.text()).toBe('224') - wrapper.setProps({ n: 358 }) - expect(div.text()).toBe('716') + const { container, rerender } = render() + const div = container.querySelector('div') + expect(div.textContent).toBe('224') + rerender() + expect(container.querySelector('div').textContent).toBe('716') }) test('componentFromStream unsubscribes from stream before unmounting', () => { @@ -34,37 +29,36 @@ test('componentFromStream unsubscribes from stream before unmounting', () => { } }) const Div = componentFromStream(() => vdom$) - const wrapper = mount(
) + const { unmount } = render(
) expect(subscriptions).toBe(1) - wrapper.unmount() + unmount() expect(subscriptions).toBe(0) }) test('componentFromStream renders nothing until the stream emits a value', () => { const vdom$ = new Subject() const Div = componentFromStream(() => vdom$.mapTo(
)) - const wrapper = mount(
) - expect(wrapper.find('div').length).toBe(0) - vdom$.next() - wrapper.update() - expect(wrapper.find('div').length).toBe(1) + const { container } = render(
) + expect(container.querySelector('div')).toBe(null) + act(() => { + vdom$.next() + }) + expect(container.querySelector('div')).not.toBe(null) }) test('handler multiple observers of props stream', () => { - const Other = () =>
const Div = componentFromStream(props$ => // Adds three observers to props stream - props$.combineLatest(props$, props$, props1 => ) + props$.combineLatest(props$, props$, props1 =>
) ) - const wrapper = mount(
) - const div = wrapper.find(Other) + const { container, rerender } = render(
) + const div = container.querySelector('div') - expect(div.prop('data-value')).toBe(1) - wrapper.setProps({ 'data-value': 2 }) - wrapper.update() - const div2 = wrapper.find(Other) - expect(div2.prop('data-value')).toBe(2) + expect(div.getAttribute('data-value')).toBe('1') + rerender(
) + const div2 = container.querySelector('div') + expect(div2.getAttribute('data-value')).toBe('2') }) test('complete props stream before unmounting', () => { @@ -85,28 +79,28 @@ test('complete props stream before unmounting', () => { return props$.combineLatest(first$, last$, props1 =>
) }) - const wrapper = mount(
) + const { container, unmount } = render(
) expect(counter).toBe(1) - expect(wrapper.find('div').length).toBe(1) + expect(container.querySelector('div')).not.toBe(null) - wrapper.unmount() + unmount() expect(counter).toBe(0) }) test('completed props stream should throw an exception', () => { const Div = componentFromStream(props$ => { - const first$ = props$.filter(() => false).first().startWith(null) + const first$ = props$ + .filter(() => false) + .first() + .startWith(null) return props$.combineLatest(first$, props1 =>
) }) - const wrapper = mount(
) - - expect(wrapper.find('div').length).toBe(1) + const { container, unmount } = render(
) - const error = sinon.stub(console, 'error') + expect(container.querySelector('div')).not.toBe(null) - expect(() => wrapper.unmount()).toThrowError(/no elements in sequence/) - expect(error.called).toBe(true) + expect(() => unmount()).toThrowError(/no elements in sequence/) }) diff --git a/src/packages/recompose/__tests__/componentFromStreamWithConfig-test.js b/src/packages/recompose/__tests__/componentFromStreamWithConfig-test.js index b8b9b130..1c8d8daf 100644 --- a/src/packages/recompose/__tests__/componentFromStreamWithConfig-test.js +++ b/src/packages/recompose/__tests__/componentFromStreamWithConfig-test.js @@ -1,5 +1,5 @@ import React from 'react' -import { mount } from 'enzyme' +import { render } from '@testing-library/react' import { Observable } from 'rxjs' import { Stream as MostStream } from 'most' import mostConfig from '../mostObservableConfig' @@ -9,23 +9,15 @@ import { componentFromStreamWithConfig } from '../componentFromStream' test('componentFromStreamWithConfig creates a stream with the correct stream type.', () => { const MostComponent = componentFromStreamWithConfig(mostConfig)(props$ => { expect(props$ instanceof MostStream).toBe(true) - return props$.map(v => -
- {String(v)} -
- ) + return props$.map(v =>
{String(v)}
) }) - mount() + render() const RXJSComponent = componentFromStreamWithConfig(rxjsConfig)(props$ => { expect(props$ instanceof Observable).toBe(true) - return props$.map(v => -
- {String(v)} -
- ) + return props$.map(v =>
{String(v)}
) }) - mount() + render() }) diff --git a/src/packages/recompose/__tests__/createSink-test.js b/src/packages/recompose/__tests__/createSink-test.js index ff90c5a1..8b166446 100644 --- a/src/packages/recompose/__tests__/createSink-test.js +++ b/src/packages/recompose/__tests__/createSink-test.js @@ -1,10 +1,9 @@ import React from 'react' -import { mount } from 'enzyme' -import sinon from 'sinon' +import { render, act } from '@testing-library/react' import { createSink, compose, withState, mapProps } from '../' test('createSink creates a React component that fires a callback when receiving new props', () => { - const spy = sinon.spy() + const spy = jest.fn() const Sink = createSink(spy) const Counter = compose( withState('counter', 'updateCounter', 0), @@ -14,17 +13,22 @@ test('createSink creates a React component that fires a callback when receiving })) )(Sink) - mount( + render(
) - const { increment } = spy.lastCall.args[0] - const getCounter = () => spy.lastCall.args[0].counter + const lastProps = () => spy.mock.lastCall[0] + const { increment } = lastProps() + const getCounter = () => lastProps().counter expect(getCounter()).toBe(0) - increment() + act(() => { + increment() + }) expect(getCounter()).toBe(1) - increment() + act(() => { + increment() + }) expect(getCounter()).toBe(2) }) diff --git a/src/packages/recompose/__tests__/defaultProps-test.js b/src/packages/recompose/__tests__/defaultProps-test.js index 8fed979b..de7d895e 100644 --- a/src/packages/recompose/__tests__/defaultProps-test.js +++ b/src/packages/recompose/__tests__/defaultProps-test.js @@ -1,27 +1,31 @@ import React from 'react' -import { shallow } from 'enzyme' +import { render } from '@testing-library/react' import { defaultProps } from '../' test('defaultProps passes additional props to base component', () => { const DoReMi = defaultProps({ 'data-so': 'do', 'data-la': 'fa' })('div') expect(DoReMi.displayName).toBe('defaultProps(div)') - const div = shallow().find('div') - expect(div.equals(
)).toBe(true) + const { container } = render() + const div = container.querySelector('div') + expect(div.getAttribute('data-so')).toBe('do') + expect(div.getAttribute('data-la')).toBe('fa') }) test('defaultProps has lower precendence than props from owner', () => { const DoReMi = defaultProps({ 'data-so': 'do', 'data-la': 'fa' })('div') - expect(DoReMi.displayName).toBe('defaultProps(div)') - const div = shallow().find('div') - expect(div.equals(
)).toBe(true) + const { container } = render() + const div = container.querySelector('div') + expect(div.getAttribute('data-so')).toBe('do') + expect(div.getAttribute('data-la')).toBe('ti') }) test('defaultProps overrides undefined owner props', () => { const DoReMi = defaultProps({ 'data-so': 'do', 'data-la': 'fa' })('div') - expect(DoReMi.displayName).toBe('defaultProps(div)') - const div = shallow().find('div') - expect(div.equals(
)).toBe(true) + const { container } = render() + const div = container.querySelector('div') + expect(div.getAttribute('data-so')).toBe('do') + expect(div.getAttribute('data-la')).toBe('fa') }) diff --git a/src/packages/recompose/__tests__/flattenProp-test.js b/src/packages/recompose/__tests__/flattenProp-test.js index 566d9870..2f7ad066 100644 --- a/src/packages/recompose/__tests__/flattenProp-test.js +++ b/src/packages/recompose/__tests__/flattenProp-test.js @@ -1,28 +1,20 @@ import React from 'react' -import { shallow } from 'enzyme' +import { render } from '@testing-library/react' import { flattenProp } from '../' test('flattenProps flattens an object prop and spreads it into the top-level props object', () => { const Counter = flattenProp('data-state')('div') expect(Counter.displayName).toBe('flattenProp(div)') - const wrapper = shallow( + const { container, rerender } = render( ) + const div = container.querySelector('div') + expect(div.getAttribute('data-pass')).toBe('through') + expect(div.getAttribute('data-counter')).toBe('1') - expect( - wrapper.equals( -
- ) - ).toBe(true) - - wrapper.setProps({ - 'data-pass': 'through', - 'data-state': { 'data-state': 1 }, - }) - expect(wrapper.equals(
)).toBe(true) + rerender() + const div2 = container.querySelector('div') + expect(div2.getAttribute('data-pass')).toBe('through') + expect(div2.getAttribute('data-state')).toBe('1') }) diff --git a/src/packages/recompose/__tests__/fromRenderProps-test.js b/src/packages/recompose/__tests__/fromRenderProps-test.js index df032c8f..e605de2d 100644 --- a/src/packages/recompose/__tests__/fromRenderProps-test.js +++ b/src/packages/recompose/__tests__/fromRenderProps-test.js @@ -1,5 +1,5 @@ import React from 'react' -import { mount } from 'enzyme' +import { render } from '@testing-library/react' import { fromRenderProps, compose, toRenderProps, defaultProps } from '../' test('fromRenderProps passes additional props to base component', () => { @@ -12,12 +12,13 @@ test('fromRenderProps passes additional props to base component', () => { )('div') expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') - const div = mount() - expect(div.html()).toBe(`
`) + const { container } = render() + expect(container.querySelector('div').getAttribute('i18n')).toBe('zh-TW') }) test('fromRenderProps passes additional props to base component with custom renderPropName', () => { - const RenderPropsComponent = ({ render }) => render({ i18n: 'zh-TW' }) + const RenderPropsComponent = ({ render: renderProp }) => + renderProp({ i18n: 'zh-TW' }) const EnhancedComponent = fromRenderProps( RenderPropsComponent, ({ i18n }) => ({ @@ -27,13 +28,14 @@ test('fromRenderProps passes additional props to base component with custom rend )('div') expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') - const div = mount() - expect(div.html()).toBe(`
`) + const { container } = render() + expect(container.querySelector('div').getAttribute('i18n')).toBe('zh-TW') }) test('fromRenderProps passes additional props to base component with 2 RenderPropsComponents', () => { const RenderPropsComponent1 = ({ children }) => children({ theme: 'dark' }) - const RenderPropsComponent2 = ({ render }) => render({ i18n: 'zh-TW' }) + const RenderPropsComponent2 = ({ render: renderProp }) => + renderProp({ i18n: 'zh-TW' }) const EnhancedComponent = compose( fromRenderProps( RenderPropsComponent1, @@ -50,8 +52,10 @@ test('fromRenderProps passes additional props to base component with 2 RenderPro 'fromRenderProps(fromRenderProps(div))' ) - const div = mount() - expect(div.html()).toBe(`
`) + const { container } = render() + const div = container.querySelector('div') + expect(div.getAttribute('theme')).toBe('dark') + expect(div.getAttribute('locale')).toBe('zh-TW') }) test('fromRenderProps meet toRenderProps', () => { @@ -67,8 +71,8 @@ test('fromRenderProps meet toRenderProps', () => { )('div') expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') - const div = mount() - expect(div.html()).toBe(`
`) + const { container } = render() + expect(container.querySelector('div').getAttribute('foo')).toBe('bar1') }) test('fromRenderProps with multiple arguments #693', () => { @@ -83,6 +87,8 @@ test('fromRenderProps with multiple arguments #693', () => { )('div') expect(EnhancedComponent.displayName).toBe('fromRenderProps(div)') - const div = mount() - expect(div.html()).toBe(`
`) + const { container } = render() + const div = container.querySelector('div') + expect(div.getAttribute('theme')).toBe('dark') + expect(div.getAttribute('data')).toBe('data') }) diff --git a/src/packages/recompose/__tests__/hoistStatics-test.js b/src/packages/recompose/__tests__/hoistStatics-test.js index 63824d90..5739697d 100644 --- a/src/packages/recompose/__tests__/hoistStatics-test.js +++ b/src/packages/recompose/__tests__/hoistStatics-test.js @@ -1,10 +1,9 @@ -import React, { createFactory } from 'react' -import { mount } from 'enzyme' -import sinon from 'sinon' +import React from 'react' +import { render } from '@testing-library/react' import { hoistStatics, mapProps } from '../' test('copies non-React static properties from base component to new component', () => { - const BaseComponent = sinon.spy(() => null) + const BaseComponent = jest.fn(() => null) BaseComponent.foo = () => {} const EnhancedComponent = hoistStatics( @@ -13,17 +12,17 @@ test('copies non-React static properties from base component to new component', expect(EnhancedComponent.foo).toBe(BaseComponent.foo) - mount() - expect(BaseComponent.firstCall.args[0].n).toBe(15) + render() + expect(BaseComponent.mock.lastCall[0].n).toBe(15) }) test('does not copy blacklisted static properties to new component ', () => { - const BaseComponent = sinon.spy(() => null) + const BaseComponent = () => null BaseComponent.foo = () => {} BaseComponent.bar = () => {} const EnhancedComponent = hoistStatics( - comp => createFactory(comp), + comp => props => React.createElement(comp, props), { bar: true } // Blacklist )(BaseComponent) diff --git a/src/packages/recompose/__tests__/lifecycle-test.js b/src/packages/recompose/__tests__/lifecycle-test.js index fce20d96..1521457f 100644 --- a/src/packages/recompose/__tests__/lifecycle-test.js +++ b/src/packages/recompose/__tests__/lifecycle-test.js @@ -1,17 +1,18 @@ import React from 'react' -import { mount } from 'enzyme' +import { render } from '@testing-library/react' import { lifecycle } from '../' test('lifecycle is a higher-order component version of React.Component', () => { const enhance = lifecycle({ - componentWillMount() { + UNSAFE_componentWillMount() { this.setState({ 'data-bar': 'baz' }) }, }) const Div = enhance('div') expect(Div.displayName).toBe('lifecycle(div)') - const div = mount(
).find('div') - expect(div.prop('data-foo')).toBe('bar') - expect(div.prop('data-bar')).toBe('baz') + const { container } = render(
) + const div = container.querySelector('div') + expect(div.getAttribute('data-foo')).toBe('bar') + expect(div.getAttribute('data-bar')).toBe('baz') }) diff --git a/src/packages/recompose/__tests__/mapProps-test.js b/src/packages/recompose/__tests__/mapProps-test.js index a4f2e398..59f75b70 100644 --- a/src/packages/recompose/__tests__/mapProps-test.js +++ b/src/packages/recompose/__tests__/mapProps-test.js @@ -1,10 +1,9 @@ import React from 'react' -import { mount } from 'enzyme' -import sinon from 'sinon' +import { render, act } from '@testing-library/react' import { mapProps, withState, compose } from '../' test('mapProps maps owner props to child props', () => { - const component = sinon.spy(() => null) + const component = jest.fn(() => null) component.displayName = 'component' const StringConcat = compose( @@ -17,10 +16,12 @@ test('mapProps maps owner props to child props', () => { expect(StringConcat.displayName).toBe('withState(mapProps(component))') - mount() - const { updateStrings } = component.firstCall.args[0] - updateStrings(strings => [...strings, 'fa']) + render() + const { updateStrings } = component.mock.calls[0][0] + act(() => { + updateStrings(strings => [...strings, 'fa']) + }) - expect(component.firstCall.args[0].string).toBe('doremi') - expect(component.secondCall.args[0].string).toBe('doremifa') + expect(component.mock.calls[0][0].string).toBe('doremi') + expect(component.mock.calls[1][0].string).toBe('doremifa') }) diff --git a/src/packages/recompose/__tests__/mapPropsStream-test.js b/src/packages/recompose/__tests__/mapPropsStream-test.js index 2b9dd7df..28c9e3e0 100644 --- a/src/packages/recompose/__tests__/mapPropsStream-test.js +++ b/src/packages/recompose/__tests__/mapPropsStream-test.js @@ -1,5 +1,5 @@ import React from 'react' -import { mount } from 'enzyme' +import { render } from '@testing-library/react' import setObservableConfig from '../setObservableConfig' import rxjs4Config from '../rxjs4ObservableConfig' import { mapPropsStream } from '../' @@ -11,9 +11,9 @@ test('mapPropsStream creates a higher-order component from a stream', () => { const Double = mapPropsStream(props$ => props$.map(({ n }) => ({ children: n * 2 })) )('div') - const wrapper = mount() - const div = wrapper.find('div') - expect(div.text()).toBe('224') - wrapper.setProps({ n: 358 }) - expect(div.text()).toBe('716') + const { container, rerender } = render() + const div = container.querySelector('div') + expect(div.textContent).toBe('224') + rerender() + expect(container.querySelector('div').textContent).toBe('716') }) diff --git a/src/packages/recompose/__tests__/mapPropsStreamWithConfig-test.js b/src/packages/recompose/__tests__/mapPropsStreamWithConfig-test.js index e892f3f5..f945adcd 100644 --- a/src/packages/recompose/__tests__/mapPropsStreamWithConfig-test.js +++ b/src/packages/recompose/__tests__/mapPropsStreamWithConfig-test.js @@ -1,5 +1,5 @@ import React from 'react' -import { mount } from 'enzyme' +import { render } from '@testing-library/react' import { Stream as MostStream } from 'most' import { Observable } from 'rxjs' import { mapPropsStreamWithConfig } from '../' @@ -11,11 +11,11 @@ test('mapPropsStreamWithConfig creates a higher-order component from a stream an const Double = mapPropsStreamWithConfig(rxConfig)(props$ => props$.map(({ n }) => ({ children: n * 2 })) )('div') - const wrapper = mount() - const div = wrapper.find('div') - expect(div.text()).toBe('224') - wrapper.setProps({ n: 358 }) - expect(div.text()).toBe('716') + const { container, rerender } = render() + const div = container.querySelector('div') + expect(div.textContent).toBe('224') + rerender() + expect(container.querySelector('div').textContent).toBe('716') }) test('mapPropsStreamWithConfig creates a stream with the correct config', () => { @@ -24,12 +24,12 @@ test('mapPropsStreamWithConfig creates a stream with the correct config', () => return props$.map(v => v) })('div') - mount() + render() const RXJSComponent = mapPropsStreamWithConfig(rxConfig)(props$ => { expect(props$ instanceof Observable).toBe(true) return props$.map(v => v) })('div') - mount() + render() }) diff --git a/src/packages/recompose/__tests__/nest-test.js b/src/packages/recompose/__tests__/nest-test.js index a27ad250..c9f7344f 100644 --- a/src/packages/recompose/__tests__/nest-test.js +++ b/src/packages/recompose/__tests__/nest-test.js @@ -1,5 +1,5 @@ import React from 'react' -import { shallow } from 'enzyme' +import { render } from '@testing-library/react' import { nest, setDisplayName, toClass } from '../' test('nest nests components from outer to inner', () => { @@ -11,15 +11,12 @@ test('nest nests components from outer to inner', () => { expect(Nest.displayName).toBe('nest(A, B, C)') - const wrapper = shallow(Child) - - expect( - wrapper.equals( - - - Child - - - ) - ).toBe(true) + const { container } = render(Child) + // nest(A,B,C) creates nested divs, all with pass="through" + const divs = container.querySelectorAll('div') + expect(divs.length).toBe(3) + divs.forEach(div => { + expect(div.getAttribute('pass')).toBe('through') + }) + expect(container.textContent).toBe('Child') }) diff --git a/src/packages/recompose/__tests__/onlyUpdateForKeys-test.js b/src/packages/recompose/__tests__/onlyUpdateForKeys-test.js index 71fb9905..e92e6e23 100644 --- a/src/packages/recompose/__tests__/onlyUpdateForKeys-test.js +++ b/src/packages/recompose/__tests__/onlyUpdateForKeys-test.js @@ -1,10 +1,9 @@ import React from 'react' -import { mount } from 'enzyme' -import sinon from 'sinon' +import { render, act } from '@testing-library/react' import { onlyUpdateForKeys, compose, withState } from '../' test('onlyUpdateForKeys implements shouldComponentUpdate()', () => { - const component = sinon.spy(() => null) + const component = jest.fn(() => null) component.displayName = 'component' const Counter = compose( @@ -17,18 +16,22 @@ test('onlyUpdateForKeys implements shouldComponentUpdate()', () => { 'withState(withState(onlyUpdateForKeys(component)))' ) - mount() - const { updateCounter, updateFoobar } = component.firstCall.args[0] + render() + const { updateCounter, updateFoobar } = component.mock.calls[0][0] - expect(component.lastCall.args[0].counter).toBe(0) - expect(component.lastCall.args[0].foobar).toBe('foobar') + expect(component.mock.lastCall[0].counter).toBe(0) + expect(component.mock.lastCall[0].foobar).toBe('foobar') // Does not update - updateFoobar('barbaz') - expect(component.calledOnce).toBe(true) + act(() => { + updateFoobar('barbaz') + }) + expect(component.mock.calls.length).toBe(1) - updateCounter(42) - expect(component.calledTwice).toBe(true) - expect(component.lastCall.args[0].counter).toBe(42) - expect(component.lastCall.args[0].foobar).toBe('barbaz') + act(() => { + updateCounter(42) + }) + expect(component.mock.calls.length).toBe(2) + expect(component.mock.lastCall[0].counter).toBe(42) + expect(component.mock.lastCall[0].foobar).toBe('barbaz') }) diff --git a/src/packages/recompose/__tests__/onlyUpdateForPropTypes-test.js b/src/packages/recompose/__tests__/onlyUpdateForPropTypes-test.js index 548f9250..fcae6ff5 100644 --- a/src/packages/recompose/__tests__/onlyUpdateForPropTypes-test.js +++ b/src/packages/recompose/__tests__/onlyUpdateForPropTypes-test.js @@ -1,11 +1,10 @@ import React from 'react' import PropTypes from 'prop-types' -import sinon from 'sinon' -import { mount, shallow } from 'enzyme' +import { render, act } from '@testing-library/react' import { onlyUpdateForPropTypes, compose, withState, setPropTypes } from '../' test('onlyUpdateForPropTypes only updates for props specified in propTypes', () => { - const component = sinon.spy(() => null) + const component = jest.fn(() => null) component.displayName = 'component' const Counter = compose( @@ -19,35 +18,37 @@ test('onlyUpdateForPropTypes only updates for props specified in propTypes', () 'withState(withState(onlyUpdateForPropTypes(component)))' ) - mount() - const { updateCounter, updateFoobar } = component.firstCall.args[0] + render() + const { updateCounter, updateFoobar } = component.mock.calls[0][0] - expect(component.lastCall.args[0].counter).toBe(0) - expect(component.lastCall.args[0].foobar).toBe('foobar') + expect(component.mock.lastCall[0].counter).toBe(0) + expect(component.mock.lastCall[0].foobar).toBe('foobar') // Does not update - updateFoobar('barbaz') - expect(component.calledOnce).toBe(true) - - updateCounter(42) - expect(component.calledTwice).toBe(true) - expect(component.lastCall.args[0].counter).toBe(42) - expect(component.lastCall.args[0].foobar).toBe('barbaz') + act(() => { + updateFoobar('barbaz') + }) + expect(component.mock.calls.length).toBe(1) + + act(() => { + updateCounter(42) + }) + expect(component.mock.calls.length).toBe(2) + expect(component.mock.lastCall[0].counter).toBe(42) + expect(component.mock.lastCall[0].foobar).toBe('barbaz') }) test('onlyUpdateForPropTypes warns if BaseComponent does not have any propTypes', () => { - const error = sinon.stub(console, 'error') + const error = jest.spyOn(console, 'error').mockImplementation(() => {}) const ShouldWarn = onlyUpdateForPropTypes('div') - shallow() + render() - expect(error.firstCall.args[0]).toBe( + expect(error).toHaveBeenCalledWith( 'A component without any `propTypes` was passed to ' + '`onlyUpdateForPropTypes()`. Check the implementation of the component ' + 'with display name "div".' ) - /* eslint-disable */ - console.error.restore() - /* eslint-enable */ + error.mockRestore() }) diff --git a/src/packages/recompose/__tests__/pure-test.js b/src/packages/recompose/__tests__/pure-test.js index 02ff614e..d1b5ca70 100644 --- a/src/packages/recompose/__tests__/pure-test.js +++ b/src/packages/recompose/__tests__/pure-test.js @@ -1,11 +1,10 @@ import React from 'react' -import { mount } from 'enzyme' -import sinon from 'sinon' +import { render, act } from '@testing-library/react' import { pure, compose, withState } from '../' import { countRenders } from './utils' test('pure implements shouldComponentUpdate() using shallowEqual()', () => { - const component = sinon.spy(() => null) + const component = jest.fn(() => null) component.displayName = 'component' const initialTodos = ['eat', 'drink', 'sleep'] @@ -17,18 +16,22 @@ test('pure implements shouldComponentUpdate() using shallowEqual()', () => { expect(Todos.displayName).toBe('withState(pure(countRenders(component)))') - mount() - const { updateTodos } = component.firstCall.args[0] + render() + const { updateTodos } = component.mock.calls[0][0] - expect(component.lastCall.args[0].todos).toBe(initialTodos) - expect(component.lastCall.args[0].renderCount).toBe(1) + expect(component.mock.lastCall[0].todos).toBe(initialTodos) + expect(component.mock.lastCall[0].renderCount).toBe(1) // Does not re-render - updateTodos(initialTodos) - expect(component.calledOnce).toBe(true) + act(() => { + updateTodos(initialTodos) + }) + expect(component.mock.calls.length).toBe(1) - updateTodos(todos => todos.slice(0, -1)) - expect(component.calledTwice).toBe(true) - expect(component.lastCall.args[0].todos).toEqual(['eat', 'drink']) - expect(component.lastCall.args[0].renderCount).toBe(2) + act(() => { + updateTodos(todos => todos.slice(0, -1)) + }) + expect(component.mock.calls.length).toBe(2) + expect(component.mock.lastCall[0].todos).toEqual(['eat', 'drink']) + expect(component.mock.lastCall[0].renderCount).toBe(2) }) diff --git a/src/packages/recompose/__tests__/renameProp-test.js b/src/packages/recompose/__tests__/renameProp-test.js index db4f0e93..e7bc5eb5 100644 --- a/src/packages/recompose/__tests__/renameProp-test.js +++ b/src/packages/recompose/__tests__/renameProp-test.js @@ -1,5 +1,5 @@ import React from 'react' -import { mount } from 'enzyme' +import { render } from '@testing-library/react' import { withProps, renameProp, compose } from '../' test('renameProp renames a single prop', () => { @@ -10,6 +10,8 @@ test('renameProp renames a single prop', () => { expect(StringConcat.displayName).toBe('withProps(renameProp(div))') - const div = mount().find('div') - expect(div.props()).toEqual({ 'data-do': 123, 'data-la': 456 }) + const { container } = render() + const div = container.querySelector('div') + expect(div.getAttribute('data-do')).toBe('123') + expect(div.getAttribute('data-la')).toBe('456') }) diff --git a/src/packages/recompose/__tests__/renameProps-test.js b/src/packages/recompose/__tests__/renameProps-test.js index 0eb7b9e7..d97b01a1 100644 --- a/src/packages/recompose/__tests__/renameProps-test.js +++ b/src/packages/recompose/__tests__/renameProps-test.js @@ -1,5 +1,5 @@ import React from 'react' -import { mount } from 'enzyme' +import { render } from '@testing-library/react' import { withProps, renameProps, compose } from '../' test('renameProps renames props', () => { @@ -10,8 +10,9 @@ test('renameProps renames props', () => { expect(StringConcat.displayName).toBe('withProps(renameProps(div))') - const div = mount().find('div') + const { container } = render() + const div = container.querySelector('div') - expect(div.prop('data-do')).toBe(123) - expect(div.prop('data-fa')).toBe(456) + expect(div.getAttribute('data-do')).toBe('123') + expect(div.getAttribute('data-fa')).toBe('456') }) diff --git a/src/packages/recompose/__tests__/renderComponent-test.js b/src/packages/recompose/__tests__/renderComponent-test.js index eedc125f..9456a88b 100644 --- a/src/packages/recompose/__tests__/renderComponent-test.js +++ b/src/packages/recompose/__tests__/renderComponent-test.js @@ -1,11 +1,10 @@ import React from 'react' -import { mount } from 'enzyme' -import sinon from 'sinon' +import { render, act } from '@testing-library/react' import { renderComponent, withState, compose, branch } from '../' test('renderComponent always renders the given component', () => { - const componentA = sinon.spy(() => null) - const componentB = sinon.spy(() => null) + const componentA = jest.fn(() => null) + const componentB = jest.fn(() => null) const Foobar = compose( withState('flip', 'updateFlip', false), @@ -16,13 +15,15 @@ test('renderComponent always renders the given component', () => { ) )(null) - mount() - const { updateFlip } = componentB.firstCall.args[0] + render() + const { updateFlip } = componentB.mock.calls[0][0] - expect(componentB.calledOnce).toBe(true) - expect(componentA.notCalled).toBe(true) + expect(componentB.mock.calls.length).toBe(1) + expect(componentA.mock.calls.length).toBe(0) - updateFlip(true) - expect(componentB.calledOnce).toBe(true) - expect(componentA.calledOnce).toBe(true) + act(() => { + updateFlip(true) + }) + expect(componentB.mock.calls.length).toBe(1) + expect(componentA.mock.calls.length).toBe(1) }) diff --git a/src/packages/recompose/__tests__/renderNothing-test.js b/src/packages/recompose/__tests__/renderNothing-test.js index 88820fd3..f66c1349 100644 --- a/src/packages/recompose/__tests__/renderNothing-test.js +++ b/src/packages/recompose/__tests__/renderNothing-test.js @@ -1,14 +1,13 @@ import React from 'react' -import { shallow } from 'enzyme' +import { render } from '@testing-library/react' import { renderNothing } from '../' test('renderNothing returns a component that renders null', () => { const Nothing = renderNothing('div') - const wrapper = shallow() + const { container } = render() + expect(container.innerHTML).toBe('') const Parent = () => - const parentWrapper = shallow() - - expect(wrapper.type()).toBe(null) - expect(parentWrapper.text()).toBe('') + const { container: parentContainer } = render() + expect(parentContainer.innerHTML).toBe('') }) diff --git a/src/packages/recompose/__tests__/setObservableConfig-test.js b/src/packages/recompose/__tests__/setObservableConfig-test.js index bd47133d..1f389ef6 100644 --- a/src/packages/recompose/__tests__/setObservableConfig-test.js +++ b/src/packages/recompose/__tests__/setObservableConfig-test.js @@ -1,5 +1,5 @@ import React from 'react' -import { mount } from 'enzyme' +import { render } from '@testing-library/react' import rxjs5Config from '../rxjsObservableConfig' import rxjs4Config from '../rxjs4ObservableConfig' import mostConfig from '../mostObservableConfig' @@ -12,86 +12,44 @@ import componentFromStream from '../componentFromStream' const testTransform = transform => { const Double = componentFromStream(transform) - const wrapper = mount() - const div = wrapper.find('div') - expect(div.text()).toBe('224') - wrapper.setProps({ n: 358 }) - expect(div.text()).toBe('716') + const { container, rerender } = render() + const div = container.querySelector('div') + expect(div.textContent).toBe('224') + rerender() + expect(container.querySelector('div').textContent).toBe('716') } test('works with RxJS 5', () => { setObservableConfig(rxjs5Config) - testTransform(props$ => - props$.map(({ n }) => -
- {n * 2} -
- ) - ) + testTransform(props$ => props$.map(({ n }) =>
{n * 2}
)) }) test('works with RxJS 4', () => { setObservableConfig(rxjs4Config) - testTransform(props$ => - props$.map(({ n }) => -
- {n * 2} -
- ) - ) + testTransform(props$ => props$.map(({ n }) =>
{n * 2}
)) }) test('works with most', () => { setObservableConfig(mostConfig) - testTransform(props$ => - props$.map(({ n }) => -
- {n * 2} -
- ) - ) + testTransform(props$ => props$.map(({ n }) =>
{n * 2}
)) }) test('works with xstream', () => { setObservableConfig(xstreamConfig) - testTransform(props$ => - props$.map(({ n }) => -
- {n * 2} -
- ) - ) + testTransform(props$ => props$.map(({ n }) =>
{n * 2}
)) }) test('works with bacon', () => { setObservableConfig(baconConfig) - testTransform(props$ => - props$.map(({ n }) => -
- {n * 2} -
- ) - ) + testTransform(props$ => props$.map(({ n }) =>
{n * 2}
)) }) test('works with kefir', () => { setObservableConfig(kefirConfig) - testTransform(props$ => - props$.map(({ n }) => -
- {n * 2} -
- ) - ) + testTransform(props$ => props$.map(({ n }) =>
{n * 2}
)) }) test('works with flyd', () => { setObservableConfig(flydConfig) - testTransform(props$ => - props$.map(({ n }) => -
- {n * 2} -
- ) - ) + testTransform(props$ => props$.map(({ n }) =>
{n * 2}
)) }) diff --git a/src/packages/recompose/__tests__/shouldUpdate-test.js b/src/packages/recompose/__tests__/shouldUpdate-test.js index 80b90723..db69c037 100644 --- a/src/packages/recompose/__tests__/shouldUpdate-test.js +++ b/src/packages/recompose/__tests__/shouldUpdate-test.js @@ -1,11 +1,10 @@ import React from 'react' -import { mount } from 'enzyme' -import sinon from 'sinon' +import { render, act } from '@testing-library/react' import { shouldUpdate, compose, withState } from '../' import { countRenders } from './utils' test('shouldUpdate implements shouldComponentUpdate', () => { - const component = sinon.spy(() => null) + const component = jest.fn(() => null) component.displayName = 'component' const initialTodos = ['eat', 'drink', 'sleep'] @@ -19,18 +18,22 @@ test('shouldUpdate implements shouldComponentUpdate', () => { 'withState(shouldUpdate(countRenders(component)))' ) - mount() - const { updateTodos } = component.firstCall.args[0] + render() + const { updateTodos } = component.mock.calls[0][0] - expect(component.lastCall.args[0].todos).toBe(initialTodos) - expect(component.lastCall.args[0].renderCount).toBe(1) + expect(component.mock.lastCall[0].todos).toBe(initialTodos) + expect(component.mock.lastCall[0].renderCount).toBe(1) // Does not re-render - updateTodos(initialTodos) - expect(component.calledOnce).toBe(true) + act(() => { + updateTodos(initialTodos) + }) + expect(component.mock.calls.length).toBe(1) - updateTodos(todos => todos.slice(0, -1)) - expect(component.calledTwice).toBe(true) - expect(component.lastCall.args[0].todos).toEqual(['eat', 'drink']) - expect(component.lastCall.args[0].renderCount).toBe(2) + act(() => { + updateTodos(todos => todos.slice(0, -1)) + }) + expect(component.mock.calls.length).toBe(2) + expect(component.mock.lastCall[0].todos).toEqual(['eat', 'drink']) + expect(component.mock.lastCall[0].renderCount).toBe(2) }) diff --git a/src/packages/recompose/__tests__/toClass-test.js b/src/packages/recompose/__tests__/toClass-test.js index 4d61c607..e90c520c 100644 --- a/src/packages/recompose/__tests__/toClass-test.js +++ b/src/packages/recompose/__tests__/toClass-test.js @@ -1,8 +1,7 @@ import React from 'react' import PropTypes from 'prop-types' -import { mount } from 'enzyme' -import sinon from 'sinon' -import { toClass, withContext, compose } from '../' +import { render } from '@testing-library/react' +import { toClass, withContext, getContext, compose } from '../' test('toClass returns the base component if it is already a class', () => { class BaseComponent extends React.Component { @@ -15,35 +14,32 @@ test('toClass returns the base component if it is already a class', () => { expect(TestComponent).toBe(BaseComponent) }) -test('toClass copies propTypes, displayName, contextTypes and defaultProps from base component', () => { +test('toClass copies propTypes, displayName and defaultProps from base component', () => { const StatelessComponent = () =>
StatelessComponent.displayName = 'Stateless' StatelessComponent.propTypes = { foo: PropTypes.string } - StatelessComponent.contextTypes = { bar: PropTypes.object } StatelessComponent.defaultProps = { foo: 'bar', fizz: 'buzz' } const TestComponent = toClass(StatelessComponent) expect(TestComponent.displayName).toBe('Stateless') expect(TestComponent.propTypes).toEqual({ foo: PropTypes.string }) - expect(TestComponent.contextTypes).toEqual({ bar: PropTypes.object }) expect(TestComponent.defaultProps).toEqual({ foo: 'bar', fizz: 'buzz' }) }) test('toClass passes defaultProps correctly', () => { - const StatelessComponent = sinon.spy(() => null) + const StatelessComponent = jest.fn(() => null) StatelessComponent.displayName = 'Stateless' StatelessComponent.propTypes = { foo: PropTypes.string } - StatelessComponent.contextTypes = { bar: PropTypes.object } StatelessComponent.defaultProps = { foo: 'bar', fizz: 'buzz' } const TestComponent = toClass(StatelessComponent) - mount() - expect(StatelessComponent.lastCall.args[0].foo).toBe('bar') - expect(StatelessComponent.lastCall.args[0].fizz).toBe('buzz') + render() + expect(StatelessComponent.mock.lastCall[0].foo).toBe('bar') + expect(StatelessComponent.mock.lastCall[0].fizz).toBe('buzz') }) test('toClass passes context and props correctly', () => { @@ -63,25 +59,31 @@ test('toClass passes context and props correctly', () => { withContext({ store: PropTypes.object }, props => ({ store: props.store })) )(Provider) - const StatelessComponent = (props, context) => -
+ const StatelessComponent = props => ( +
+ ) - StatelessComponent.contextTypes = { store: PropTypes.object } + const TestComponent = compose( + getContext({ store: PropTypes.object }), + toClass + )(StatelessComponent) - const TestComponent = toClass(StatelessComponent) - - const div = mount( + const { container } = render( - ).find('div') + ) - expect(div.prop('data-props').fizz).toBe('fizzbuzz') - expect(div.prop('data-context').store).toBe(store) + const div = container.querySelector('div') + expect(div.getAttribute('data-fizz')).toBe('fizzbuzz') + expect(div.getAttribute('data-has-store')).toBe('yes') }) test('toClass works with strings (DOM components)', () => { const Div = toClass('div') - const div = mount(
Hello
) - expect(div.html()).toBe('
Hello
') + const { container } = render(
Hello
) + expect(container.innerHTML).toBe('
Hello
') }) diff --git a/src/packages/recompose/__tests__/toRenderProps-test.js b/src/packages/recompose/__tests__/toRenderProps-test.js index e8d35ceb..c2a13392 100644 --- a/src/packages/recompose/__tests__/toRenderProps-test.js +++ b/src/packages/recompose/__tests__/toRenderProps-test.js @@ -1,5 +1,5 @@ import React from 'react' -import { mount } from 'enzyme' +import { render } from '@testing-library/react' import { toRenderProps, defaultProps } from '../' test('toRenderProps creates a component from defaultProps HOC', () => { @@ -8,14 +8,10 @@ test('toRenderProps creates a component from defaultProps HOC', () => { expect(Enhanced.displayName).toBe('defaultProps(RenderPropsComponent)') - const h1 = mount( - - {({ foo }) => -

- {foo} -

} -
- ).find('h1') + const { container } = render( + {({ foo }) =>

{foo}

}
+ ) - expect(h1.html()).toBe(`

bar

`) + const h1 = container.querySelector('h1') + expect(h1.innerHTML).toBe('bar') }) diff --git a/src/packages/recompose/__tests__/withContext-test.js b/src/packages/recompose/__tests__/withContext-test.js index 1b138c83..6b4939a6 100644 --- a/src/packages/recompose/__tests__/withContext-test.js +++ b/src/packages/recompose/__tests__/withContext-test.js @@ -1,8 +1,7 @@ /* eslint-disable react/require-default-props */ import React, { Component } from 'react' import PropTypes from 'prop-types' -import { mount } from 'enzyme' -import sinon from 'sinon' +import { render } from '@testing-library/react' import { withContext, getContext, compose, mapProps } from '../' test('withContext + getContext adds to and grabs from context', () => { @@ -36,18 +35,18 @@ test('withContext + getContext adds to and grabs from context', () => { mapProps(props => selector(props.store.getState())) ) - const component = sinon.spy(() => null) + const component = jest.fn(() => null) component.displayName = 'component' const TodoList = connect(({ todos }) => ({ todos }))(component) expect(TodoList.displayName).toBe('getContext(mapProps(component))') - mount( + render( ) - expect(component.lastCall.args[0].todos).toEqual(['eat', 'drink', 'sleep']) + expect(component.mock.lastCall[0].todos).toEqual(['eat', 'drink', 'sleep']) }) diff --git a/src/packages/recompose/__tests__/withHandlers-test.js b/src/packages/recompose/__tests__/withHandlers-test.js index 4633865a..a73e6d8d 100644 --- a/src/packages/recompose/__tests__/withHandlers-test.js +++ b/src/packages/recompose/__tests__/withHandlers-test.js @@ -1,6 +1,5 @@ import React from 'react' -import { mount } from 'enzyme' -import sinon from 'sinon' +import { render, fireEvent } from '@testing-library/react' import { withHandlers, withState, compose } from '../' test('withHandlers passes handlers to base component', () => { @@ -17,30 +16,29 @@ test('withHandlers passes handlers to base component', () => { }) ) - const Form = enhanceForm(({ value, onChange, onSubmit }) => + const Form = enhanceForm(({ value, onChange, onSubmit }) => (
-

- {value} -

+

{value}

- ) + )) - const wrapper = mount(
) - const input = wrapper.find('input') - const output = wrapper.find('p') - const form = wrapper.find('form') + const { container } = render() - input.simulate('change', { target: { value: 'Yay' } }) - expect(output.text()).toBe('Yay') + fireEvent.change(container.querySelector('input'), { + target: { value: 'Yay' }, + }) + expect(container.querySelector('p').textContent).toBe('Yay') - input.simulate('change', { target: { value: 'Yay!!' } }) - expect(output.text()).toBe('Yay!!') + fireEvent.change(container.querySelector('input'), { + target: { value: 'Yay!!' }, + }) + expect(container.querySelector('p').textContent).toBe('Yay!!') - form.simulate('submit') + fireEvent.submit(container.querySelector('form')) expect(submittedFormValue).toBe('Yay!!') }) @@ -48,38 +46,42 @@ test('withHandlers passes immutable handlers', () => { const enhance = withHandlers({ handler: () => () => null, }) - const component = sinon.spy(() => null) + const component = jest.fn(() => null) const Div = enhance(component) - const wrapper = mount(
) - wrapper.setProps({ foo: 'bar' }) + const { rerender } = render(
) + rerender(
) - expect(component.calledTwice).toBe(true) - expect(component.firstCall.args[0].handler).toBe( - component.secondCall.args[0].handler + expect(component.mock.calls.length).toBe(2) + expect(component.mock.calls[0][0].handler).toBe( + component.mock.calls[1][0].handler ) }) test('withHandlers warns if handler is not a higher-order function', () => { - const error = sinon.stub(console, 'error') + jest.spyOn(console, 'error').mockImplementation(() => {}) const Button = withHandlers({ onClick: () => {}, })('button') - const wrapper = mount(