Skip to content

Commit 081554e

Browse files
authored
Merge pull request #2041 from pgangwani/master + #2008 from chenesan/react-hooks
[Hooks Testing] Added Test cases for useState, useEffects & customHooks
2 parents d2bb655 + d2a3171 commit 081554e

File tree

9 files changed

+662
-5
lines changed

9 files changed

+662
-5
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"test:watch": "mocha --recursive --watch packages/enzyme-test-suite/test",
2828
"pretest:karma": "npm run build",
2929
"test:karma": "karma start",
30-
"test:all": "npm run react 13 && npm run test:only && npm run react 14 && npm run test:only && npm run react 15 && npm run test:only && npm run react 15.4 && npm run test:only && npm run react 15.5 && npm run test:only && npm run react 16 && npm run test:only && npm run react 16.1 && npm run test:only && npm run react 16.2 && npm run test:only && npm run react 16.3 && npm run test:only && npm run react 16.4 && npm run test:only && npm run react 16.5 && npm run test:only",
30+
"test:all": "npm run react 13 && npm run test:only && npm run react 14 && npm run test:only && npm run react 15 && npm run test:only && npm run react 15.4 && npm run test:only && npm run react 15.5 && npm run test:only && npm run react 16 && npm run test:only && npm run react 16.1 && npm run test:only && npm run react 16.2 && npm run test:only && npm run react 16.3 && npm run test:only && npm run react 16.4 && npm run test:only && npm run react 16.5 && npm run test:only && npm run react 16.8 && npm run test:only",
3131
"react": "sh install-relevant-react.sh",
3232
"env": "babel-node ./env.js",
3333
"docs:clean": "rimraf _book",

packages/enzyme-test-suite/test/ReactWrapper-spec.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,9 +1047,13 @@ describeWithDOM('mount', () => {
10471047
describeHooks(
10481048
{ Wrap, Wrapper },
10491049
'useCallback',
1050+
'useContext',
10501051
'useEffect',
10511052
'useLayoutEffect',
10521053
'useMemo',
1054+
'useReducer',
1055+
'useState',
1056+
'custom',
10531057
);
10541058

10551059
describeIf(is('>= 16.6'), 'Suspense & lazy', () => {

packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,9 +1228,13 @@ describe('shallow', () => {
12281228
describeHooks(
12291229
{ Wrap, Wrapper },
12301230
'useCallback',
1231+
'useContext',
12311232
'useEffect',
12321233
'useLayoutEffect',
12331234
'useMemo',
1235+
'useReducer',
1236+
'useState',
1237+
'custom',
12341238
);
12351239

12361240
describe('.shallow()', () => {

packages/enzyme-test-suite/test/shared/hooks/_hook.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919

2020
import {
2121
useCallback,
22+
useContext,
2223
useEffect,
2324
useLayoutEffect,
2425
useMemo,
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import sinon from 'sinon-sandbox';
4+
5+
import {
6+
describeIf,
7+
} from '../../_helpers';
8+
9+
import {
10+
useEffect,
11+
useState,
12+
} from '../../_helpers/react-compat';
13+
14+
export default function describeCustomHooks({
15+
hasHooks,
16+
Wrap,
17+
isShallow,
18+
}) {
19+
describeIf(hasHooks, 'hooks: custom', () => {
20+
describe('custom hook : useCounter', () => {
21+
function useCounter({ initialCount = 0, step = 1 } = {}) {
22+
const [count, setCount] = useState(initialCount);
23+
const increment = () => setCount(c => c + step);
24+
const decrement = () => setCount(c => c - step);
25+
return { count, increment, decrement };
26+
}
27+
// testing custom hooks with renderProps
28+
// may be we can think of adding in utils
29+
// will be repeated
30+
const Counter = ({ children, ...rest }) => children(useCounter(rest));
31+
32+
function setup(props) {
33+
const returnVal = {};
34+
Wrap(
35+
<Counter {...props}>
36+
{(val) => {
37+
Object.assign(returnVal, val);
38+
return null;
39+
}}
40+
</Counter>,
41+
);
42+
return returnVal;
43+
}
44+
45+
it('useCounter', () => {
46+
const counterData = setup();
47+
counterData.increment();
48+
expect(counterData).to.have.property('count', 1);
49+
counterData.decrement();
50+
expect(counterData).to.have.property('count', 0);
51+
});
52+
53+
it('useCounter with initialCount', () => {
54+
const counterData = setup({ initialCount: 2 });
55+
counterData.increment();
56+
expect(counterData).to.have.property('count', 3);
57+
counterData.decrement();
58+
expect(counterData).to.have.property('count', 2);
59+
});
60+
61+
it('useCounter with step', () => {
62+
const counterData = setup({ step: 2 });
63+
counterData.increment();
64+
expect(counterData).to.have.property('count', 2);
65+
counterData.decrement();
66+
expect(counterData).to.have.property('count', 0);
67+
});
68+
69+
it('useCounter with step and initialCount', () => {
70+
const counterData = setup({ step: 2, initialCount: 5 });
71+
counterData.increment();
72+
expect(counterData).to.have.property('count', 7);
73+
counterData.decrement();
74+
expect(counterData).to.have.property('count', 5);
75+
});
76+
});
77+
78+
// todo: enable shallow when useEffect works in the shallow renderer. see https://github.com/facebook/react/issues/15275
79+
describeIf(!isShallow, 'custom hook: formInput invoke props', () => {
80+
function useFormInput(initialValue = '') {
81+
const [value, setValue] = useState(initialValue);
82+
83+
return {
84+
value,
85+
onChange(e) {
86+
setValue(e.target.value);
87+
},
88+
};
89+
}
90+
91+
function Input(props) {
92+
return (
93+
<div>
94+
<input {...props} />
95+
</div>
96+
);
97+
}
98+
99+
function ControlledInputWithEnhancedInput({ searchSomething }) {
100+
const search = useFormInput();
101+
102+
useEffect(
103+
() => {
104+
searchSomething(search.value);
105+
},
106+
[search.value],
107+
);
108+
109+
return <Input {...search} />;
110+
}
111+
112+
function ControlledInputWithNativeInput({ searchSomething }) {
113+
const search = useFormInput();
114+
115+
useEffect(
116+
() => {
117+
searchSomething(search.value);
118+
},
119+
[search.value],
120+
);
121+
122+
return <input {...search} />;
123+
}
124+
125+
it('work with native input', () => {
126+
const spy = sinon.spy();
127+
const wrapper = Wrap(<ControlledInputWithNativeInput searchSomething={spy} />);
128+
wrapper.find('input').invoke('onChange')({ target: { value: 'foo' } });
129+
130+
expect(spy.withArgs('foo')).to.have.property('callCount', 1);
131+
});
132+
133+
it('work with custom wrapped Input', () => {
134+
const spy = sinon.spy();
135+
const wrapper = Wrap(<ControlledInputWithEnhancedInput searchSomething={spy} />);
136+
const input = wrapper.find('Input');
137+
input.invoke('onChange')({ target: { value: 'foo' } });
138+
expect(spy.withArgs('foo')).to.have.property('callCount', 1);
139+
});
140+
141+
it('work with custom wrapped input', () => {
142+
const spy = sinon.spy();
143+
const wrapper = Wrap(<ControlledInputWithEnhancedInput searchSomething={spy} />);
144+
const input = wrapper.find('input');
145+
input.invoke('onChange')({ target: { value: 'foo' } });
146+
expect(spy.withArgs('foo')).to.have.property('callCount', 1);
147+
});
148+
});
149+
});
150+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
4+
import {
5+
describeIf,
6+
itIf,
7+
} from '../../_helpers';
8+
9+
import {
10+
useContext,
11+
useState,
12+
createContext,
13+
} from '../../_helpers/react-compat';
14+
15+
export default function describeUseContext({
16+
hasHooks,
17+
Wrap,
18+
isShallow,
19+
}) {
20+
describeIf(hasHooks, 'hooks: useContext', () => {
21+
describe('simple example', () => {
22+
const initialTitle = 'initialTitle';
23+
const TitleContext = createContext && createContext(initialTitle);
24+
25+
function UiComponent() {
26+
const title = useContext(TitleContext);
27+
return (
28+
<div>
29+
{title}
30+
</div>
31+
);
32+
}
33+
34+
const customTitle = 'CustomTitle';
35+
36+
function App() {
37+
return (
38+
<TitleContext.Provider value={customTitle}>
39+
<UiComponent />
40+
</TitleContext.Provider>
41+
);
42+
}
43+
44+
it('render ui component with initial context value', () => {
45+
const wrapper = Wrap(<UiComponent />);
46+
expect(wrapper.text()).to.equal(initialTitle);
47+
});
48+
49+
// TODO: useContext: enable when shallow dive supports createContext
50+
itIf(!isShallow, 'render ui component with value from outer provider', () => {
51+
const wrapper = Wrap(<App />);
52+
const subWrapper = isShallow ? wrapper.dive().dive() : wrapper;
53+
expect(subWrapper.text()).to.equal(customTitle);
54+
});
55+
});
56+
57+
// TODO: useContext: enable when shallow dive supports createContext
58+
describeIf(!isShallow, 'useContext: with Setting', () => {
59+
const initialState = 10;
60+
const context = createContext && createContext(null);
61+
62+
function MyGrandChild() {
63+
const myContextVal = useContext(context);
64+
65+
const increment = () => {
66+
myContextVal.setState(myContextVal.state + 1);
67+
};
68+
69+
return (
70+
<div>
71+
<button type="button" onClick={increment}>increment</button>
72+
<span className="grandChildState">
73+
{myContextVal.state}
74+
</span>
75+
</div>
76+
);
77+
}
78+
79+
function MyChild() {
80+
return (
81+
<div>
82+
<MyGrandChild />
83+
</div>
84+
);
85+
}
86+
87+
function App() {
88+
const [state, setState] = useState(initialState);
89+
90+
return (
91+
<context.Provider value={{ state, setState }}>
92+
<div>
93+
<MyChild />
94+
</div>
95+
</context.Provider>
96+
);
97+
}
98+
99+
it('test render, get and set context value ', () => {
100+
const wrapper = Wrap(<App />);
101+
102+
function getChild() {
103+
const child = wrapper.find(MyChild);
104+
return isShallow ? child.dive() : child;
105+
}
106+
function getGrandChild() {
107+
const grandchild = getChild().find(MyGrandChild);
108+
return isShallow ? grandchild.dive() : grandchild;
109+
}
110+
expect(getGrandChild().find('.grandChildState').debug()).to.equal(`<span className="grandChildState">
111+
${String(initialState)}
112+
</span>`);
113+
114+
getGrandChild().find('button').props().onClick();
115+
wrapper.update();
116+
expect(getGrandChild().find('.grandChildState').debug()).to.equal(`<span className="grandChildState">
117+
${String(initialState + 1)}
118+
</span>`);
119+
});
120+
});
121+
});
122+
}

0 commit comments

Comments
 (0)