Skip to content

Commit 06e5915

Browse files
authored
Add React 17 and 18 as valid peerDependencies (#26)
- Confirm `react-hooks-compose` is compatible with React 17 and 18 - Bump dependencies - Remove Enzyme, convert tests to React Testing Library Resolves #25
1 parent 255c148 commit 06e5915

File tree

8 files changed

+2288
-2491
lines changed

8 files changed

+2288
-2491
lines changed

.github/workflows/publish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ jobs:
88
runs-on: ubuntu-latest
99
steps:
1010
- uses: actions/checkout@v2
11-
- uses: actions/setup-node@v2
11+
- uses: actions/setup-node@v3
1212
with:
13-
node-version: '14.x'
13+
node-version: '18.x'
1414
registry-url: 'https://registry.npmjs.org'
15-
- uses: c-hive/gha-yarn-cache@v1
15+
cache: yarn
1616
- run: yarn
1717
- run: npx helloitsjoe/release-toolkit publish
1818
env:

.github/workflows/verify.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ jobs:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
node-version: [14.x, 16.x]
11+
# Node 16.17 includes npm 8.15 which has a bug in npx
12+
node-version: [16.16.x, 18.x]
1213
steps:
1314
- uses: actions/checkout@v2
14-
- uses: actions/setup-node@v2
15+
- uses: actions/setup-node@v3
1516
with:
1617
node-version: ${{ matrix.node-version }}
17-
- uses: c-hive/gha-yarn-cache@v1
18+
cache: yarn
1819
- run: yarn
1920
- run: npx helloitsjoe/release-toolkit verify
2021
- run: yarn test

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## [2.0.19](https://github.com/helloitsjoe/react-hooks-compose/releases/tag/v2.0.19) (2022-09-05)
2+
3+
**Chore**
4+
5+
- Include React 17 and 18 in `peerDependencies`
6+
- Remove `enzyme` and convert tests to React Testing Library
7+
- Upgrade dependencies
8+
19
## [2.0.18](https://github.com/helloitsjoe/react-hooks-compose/releases/tag/v2.0.18) (2022-03-28)
210

311
**Chore**

jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
testEnvironment: 'jsdom'
3+
};

package.json

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "react-hooks-compose",
3-
"version": "2.0.18",
3+
"version": "2.0.19",
44
"description": "Compose React Hooks",
55
"main": "dist/main.js",
66
"scripts": {
7-
"test": "jest --coverage --testPathPattern=__tests__/index.test.js",
8-
"release": "npx helloitsjoe/release-toolkit release",
7+
"test": "jest --coverage",
8+
"release": "npx github:helloitsjoe/release-toolkit release",
99
"lint": "eslint ./src",
1010
"coveralls": "cat ./coverage/lcov.info | coveralls",
1111
"watch": "webpack --watch",
@@ -37,35 +37,29 @@
3737
"pre-push": "npm t -- --silent"
3838
}
3939
},
40-
"jest": {
41-
"setupFilesAfterEnv": [
42-
"./src/setupTests.js"
43-
]
44-
},
4540
"homepage": "https://github.com/helloitsjoe/react-hooks-compose#readme",
4641
"peerDependencies": {
47-
"react": "^16.8"
42+
"react": "^16.8 || ^17 || ^18"
4843
},
4944
"devDependencies": {
5045
"@babel/core": "^7.11.6",
51-
"@testing-library/react": "^11.0.2",
46+
"@testing-library/react": "^13.4.0",
5247
"babel-eslint": "^10.0.3",
5348
"babel-loader": "^8.0.6",
5449
"babel-react-simple": "^1.0.2",
5550
"coveralls": "^3.0.9",
56-
"enzyme": "^3.11.0",
57-
"enzyme-adapter-react-16": "^1.15.2",
5851
"eslint": "^6.8.0",
5952
"eslint-config-helloitsjoe": "^1.2.2",
6053
"eslint-plugin-import": "^2.19.1",
6154
"eslint-plugin-jsx-a11y": "^6.2.3",
6255
"eslint-plugin-react": "^7.17.0",
6356
"eslint-plugin-react-hooks": "^4.2.0",
6457
"husky": "^3.1.0",
65-
"jest": "^26.0.1",
58+
"jest": "^29.0.2",
59+
"jest-environment-jsdom": "^29.0.2",
6660
"prettier": "^1.19.1",
67-
"react": "^16.12.0",
68-
"react-dom": "^16.12.0",
61+
"react": "^18",
62+
"react-dom": "^18",
6963
"webpack": "^4.41.4",
7064
"webpack-cli": "^3.3.10",
7165
"webpack-simple": "^1.5.2"

src/__tests__/index.test.js

Lines changed: 79 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
/* eslint-disable react/prop-types */
22
/* eslint-disable react/button-has-type */
33
import React, { useState, useContext, useEffect } from 'react';
4-
// TODO: Convert to RTL
5-
import { shallow, mount } from 'enzyme';
6-
import { render, fireEvent, waitFor } from '@testing-library/react';
4+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
75
import composeHooks from '../index';
86

97
const INITIAL_COUNT = 0;
@@ -27,66 +25,73 @@ const useUseState = () => useState(INITIAL_COUNT);
2725
const TestComponent = ({ text }) => <div>{text}</div>;
2826

2927
TestComponent.defaultProps = {
30-
text: 'Test',
28+
text: 'Test'
3129
};
3230

3331
test('passes custom hooks to component', () => {
34-
const Container = composeHooks({ useCount, useChange })(TestComponent);
35-
const wrapper = shallow(<Container />);
36-
const { count, increment, decrement, value, onChange } = wrapper
37-
.find(TestComponent)
38-
.props();
39-
expect(count).toBe(INITIAL_COUNT);
40-
expect(value).toBe(INITIAL_VALUE);
41-
expect(typeof increment).toBe('function');
42-
expect(typeof decrement).toBe('function');
43-
expect(typeof onChange).toBe('function');
32+
const MockComponent = jest.fn(() => <div>Test</div>);
33+
const Container = composeHooks({ useCount, useChange })(MockComponent);
34+
render(<Container />);
35+
expect(MockComponent.mock.calls[0][0]).toEqual({
36+
count: INITIAL_COUNT,
37+
value: INITIAL_VALUE,
38+
increment: expect.any(Function),
39+
decrement: expect.any(Function),
40+
onChange: expect.any(Function)
41+
});
4442
});
4543

4644
test('passes props to component', () => {
47-
const Container = composeHooks({ useChange })(TestComponent);
48-
const wrapper = shallow(<Container foo="bar" />);
49-
const { foo } = wrapper.find(TestComponent).props();
50-
expect(foo).toBe('bar');
45+
const MockComponent = jest.fn(() => <div>Test</div>);
46+
const Container = composeHooks({ useChange })(MockComponent);
47+
render(<Container foo="bar" />);
48+
expect(MockComponent.mock.calls[0][0].foo).toBe('bar');
5149
});
5250

5351
test('hooks work as expected', () => {
5452
const Component = ({ value, onChange }) => (
55-
<input value={value} onChange={onChange} />
53+
<label>
54+
Testing
55+
<input value={value} onChange={onChange} />
56+
</label>
5657
);
5758
const Container = composeHooks({ useChange })(Component);
58-
const wrapper = mount(<Container />);
59-
expect(wrapper.find('input').props().value).toBe(INITIAL_VALUE);
60-
wrapper.find('input').simulate('change', { target: { value: 'new' } });
61-
expect(wrapper.find('input').props().value).toBe('new');
59+
render(<Container />);
60+
expect(screen.getByLabelText(/testing/i).value).toBe(INITIAL_VALUE);
61+
fireEvent.change(screen.getByLabelText(/testing/i), {
62+
target: { value: 'new' }
63+
});
64+
expect(screen.getByLabelText(/testing/i).value).toBe('new');
6265
});
6366

6467
test('works with useContext', () => {
6568
const TestContext = React.createContext();
6669
const Component = ({ value }) => <div>{value}</div>;
67-
// eslint-disable-next-line react-hooks/rules-of-hooks
68-
const Container = composeHooks({ value: () => useContext(TestContext) })(
69-
Component
70-
);
71-
const wrapper = mount(
70+
const Container = composeHooks({
71+
value: function ContextFn() {
72+
return useContext(TestContext);
73+
}
74+
})(Component);
75+
render(
7276
<TestContext.Provider value="Hello">
7377
<Container />
7478
</TestContext.Provider>
7579
);
76-
expect(wrapper.text()).toBe('Hello');
80+
expect(screen.getByText(/hello/i)).toBeTruthy();
7781
});
7882

7983
test('works with custom hook that returns array', () => {
8084
const Component = ({ simpleHook }) => {
8185
const [count, setCount] = simpleHook;
8286
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
8387
};
84-
8588
const Container = composeHooks({ simpleHook: useUseState })(Component);
86-
const wrapper = mount(<Container />);
87-
expect(wrapper.text()).toBe(INITIAL_COUNT.toString());
88-
wrapper.find('button').simulate('click');
89-
expect(wrapper.text()).toBe((INITIAL_COUNT + 1).toString());
89+
render(<Container />);
90+
expect(screen.getByRole('button').textContent).toBe(INITIAL_COUNT.toString());
91+
fireEvent.click(screen.getByRole('button'));
92+
expect(screen.getByRole('button').textContent).toBe(
93+
(INITIAL_COUNT + 1).toString()
94+
);
9095
});
9196

9297
test('works with custom hook that returns single value', () => {
@@ -106,20 +111,20 @@ test('works with custom hook that returns single value', () => {
106111
<button onClick={() => setFoo('after')}>{bar}</button>
107112
);
108113
const Container = composeHooks({ setFoo: useFoo, bar: useBar })(Component);
109-
const wrapper = mount(<Container />);
114+
render(<Container />);
110115
expect(outerFoo).toBe('before');
111-
wrapper.find({ children: 'Click me' }).simulate('click');
116+
fireEvent.click(screen.getByRole('button'));
112117
expect(outerFoo).toBe('after');
113118
});
114119

115120
test('can pass props to hooks via function', () => {
116121
const TEST_VALUE = 'test-value';
117122
const Component = ({ value }) => value;
118123
const Container = composeHooks(props => ({
119-
useChange: () => useChange(props.initialValue),
124+
useChange: () => useChange(props.initialValue)
120125
}))(Component);
121-
const wrapper = mount(<Container initialValue={TEST_VALUE} />);
122-
expect(wrapper.text()).toBe(TEST_VALUE);
126+
render(<Container initialValue={TEST_VALUE} />);
127+
expect(screen.getByText(TEST_VALUE)).toBeTruthy();
123128
});
124129

125130
test('useEffect from custom hook', () => {
@@ -144,8 +149,8 @@ test('useEffect from custom hook', () => {
144149
describe('Edge cases', () => {
145150
it('returns component if no hooks', () => {
146151
const Container = composeHooks()(TestComponent);
147-
const wrapper = shallow(<Container text="some text" />);
148-
expect(wrapper.html()).toMatchInlineSnapshot(`"<div>some text</div>"`);
152+
render(<Container text="some text" />);
153+
expect(screen.getByText(/some text/i)).toBeTruthy();
149154
});
150155

151156
it('throws if no component', () => {
@@ -214,14 +219,14 @@ describe('React.memo', () => {
214219
const { setNameOne, setNameTwo } = useContext(TestContext);
215220
return (
216221
<>
217-
<input
218-
onChange={e => setNameOne(e.target.value)}
219-
placeholder="Name One"
220-
/>
221-
<input
222-
onChange={e => setNameTwo(e.target.value)}
223-
placeholder="Name Two"
224-
/>
222+
<label>
223+
Name One
224+
<input onChange={e => setNameOne(e.target.value)} />
225+
</label>
226+
<label>
227+
Name Two
228+
<input onChange={e => setNameTwo(e.target.value)} />
229+
</label>
225230
</>
226231
);
227232
};
@@ -258,39 +263,39 @@ describe('React.memo', () => {
258263
});
259264

260265
it('renders memoized child when props update', () => {
261-
const { getByText } = render(<Parent />);
266+
render(<Parent />);
262267
expect(rendersOne).toBe(1);
263268
expect(rendersTwo).toBe(1);
264269

265-
fireEvent.click(getByText('Update Child Two Props'));
270+
fireEvent.click(screen.getByText('Update Child Two Props'));
266271
expect(rendersOne).toBe(2);
267272
expect(rendersTwo).toBe(2);
268273
});
269274

270275
it('does NOT re-render memoized child when child 2 props update', () => {
271-
const { getByText } = render(<Parent />);
272-
fireEvent.click(getByText('Update Child One Props'));
276+
render(<Parent />);
277+
fireEvent.click(screen.getByText('Update Child One Props'));
273278
expect(rendersOne).toBe(2);
274279
expect(rendersTwo).toBe(1);
275280
});
276281

277282
it('does NOT render memoized child when non-subscribed context value updates', () => {
278-
const { getByPlaceholderText, getByTestId } = render(<Parent />);
279-
fireEvent.change(getByPlaceholderText(/name one/i), {
280-
target: { value: 'Calvin' },
283+
render(<Parent />);
284+
fireEvent.change(screen.getByLabelText(/name one/i), {
285+
target: { value: 'Calvin' }
281286
});
282287
expect(rendersOne).toBe(2);
283288
expect(rendersTwo).toBe(1);
284289
// All updates via Context so parent should not rerender
285290
expect(rendersParent).toBe(1);
286-
expect(getByTestId('one').textContent).toBe('Calvin');
287-
expect(getByTestId('two').textContent).toBe('');
291+
expect(screen.getByTestId('one').textContent).toBe('Calvin');
292+
expect(screen.getByTestId('two').textContent).toBe('');
288293
});
289294

290295
it('renders memoized child when subscribed context value changes', () => {
291-
const { getByPlaceholderText, getByTestId } = render(<Parent />);
292-
fireEvent.change(getByPlaceholderText(/name two/i), {
293-
target: { value: 'Hobbes' },
296+
const { getByTestId } = render(<Parent />);
297+
fireEvent.change(screen.getByLabelText(/name two/i), {
298+
target: { value: 'Hobbes' }
294299
});
295300
expect(rendersOne).toBe(2);
296301
expect(rendersTwo).toBe(2);
@@ -319,13 +324,12 @@ describe('Naming collisions', () => {
319324
});
320325

321326
it('if prop and hook names collide, props win (not including defaultProps)', () => {
327+
const MockComponent = jest.fn(() => <div>Test</div>);
322328
const Container = composeHooks({ useOne, useNumber, useBool, useNull })(
323-
TestComponent
329+
MockComponent
324330
);
325331
// Check falsy values, should warn for everything but undefined
326-
const wrapper = mount(
327-
<Container text="" number={0} bool={false} null={null} />
328-
);
332+
render(<Container text="" number={0} bool={false} null={null} />);
329333
const [first, second, third, fourth] = console.warn.mock.calls;
330334
expect(first[0]).toMatchInlineSnapshot(
331335
`"prop 'text' exists, overriding with value: ''"`
@@ -339,26 +343,28 @@ describe('Naming collisions', () => {
339343
expect(fourth[0]).toMatchInlineSnapshot(
340344
`"prop 'null' exists, overriding with value: 'null'"`
341345
);
342-
expect(wrapper.find(TestComponent).props().text).toBe('');
343-
expect(wrapper.find(TestComponent).props().number).toBe(0);
344-
expect(wrapper.find(TestComponent).props().bool).toBe(false);
345-
expect(wrapper.find(TestComponent).props().null).toBe(null);
346+
const firstMockCall = MockComponent.mock.calls[0][0];
347+
expect(firstMockCall.text).toBe('');
348+
expect(firstMockCall.number).toBe(0);
349+
expect(firstMockCall.bool).toBe(false);
350+
expect(firstMockCall.null).toBe(null);
346351
});
347352

348353
it('hooks override defaultProps', () => {
349354
const Container = composeHooks({ useOne })(TestComponent);
350355
const { container } = render(<Container />);
351-
const { container: test } = render(<TestComponent />);
352-
expect(test.textContent).toBe('Test');
353356
expect(container.textContent).toBe('one');
357+
const { container: rawContainer } = render(<TestComponent />);
358+
expect(rawContainer.textContent).toBe('Test');
354359
});
355360

356361
it('if multiple hook value names collide, last one wins', () => {
357362
const Container = composeHooks({ useOne, useTwo })(TestComponent);
358-
const wrapper = mount(<Container />);
363+
render(<Container />);
359364
expect(console.warn.mock.calls[0][0]).toMatchInlineSnapshot(
360365
`"prop 'text' exists, overriding with value: 'two'"`
361366
);
362-
expect(wrapper.find(TestComponent).text()).toBe('two');
367+
expect(screen.queryByText('two')).toBeTruthy();
368+
expect(screen.queryByText('text')).not.toBeTruthy();
363369
});
364370
});

src/setupTests.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)