Skip to content

Commit

Permalink
Merge pull request #2041 from pgangwani/master + #2008 from chenesan/…
Browse files Browse the repository at this point in the history
…react-hooks

[Hooks Testing] Added Test cases for useState, useEffects & customHooks
  • Loading branch information
ljharb committed Jun 11, 2019
2 parents d2bb655 + d2a3171 commit 081554e
Show file tree
Hide file tree
Showing 9 changed files with 662 additions and 5 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -27,7 +27,7 @@
"test:watch": "mocha --recursive --watch packages/enzyme-test-suite/test",
"pretest:karma": "npm run build",
"test:karma": "karma start",
"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",
"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",
"react": "sh install-relevant-react.sh",
"env": "babel-node ./env.js",
"docs:clean": "rimraf _book",
Expand Down
4 changes: 4 additions & 0 deletions packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
Expand Up @@ -1047,9 +1047,13 @@ describeWithDOM('mount', () => {
describeHooks(
{ Wrap, Wrapper },
'useCallback',
'useContext',
'useEffect',
'useLayoutEffect',
'useMemo',
'useReducer',
'useState',
'custom',
);

describeIf(is('>= 16.6'), 'Suspense & lazy', () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Expand Up @@ -1228,9 +1228,13 @@ describe('shallow', () => {
describeHooks(
{ Wrap, Wrapper },
'useCallback',
'useContext',
'useEffect',
'useLayoutEffect',
'useMemo',
'useReducer',
'useState',
'custom',
);

describe('.shallow()', () => {
Expand Down
Expand Up @@ -19,6 +19,7 @@ import {

import {
useCallback,
useContext,
useEffect,
useLayoutEffect,
useMemo,
Expand Down
150 changes: 150 additions & 0 deletions packages/enzyme-test-suite/test/shared/hooks/custom.jsx
@@ -0,0 +1,150 @@
import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon-sandbox';

import {
describeIf,
} from '../../_helpers';

import {
useEffect,
useState,
} from '../../_helpers/react-compat';

export default function describeCustomHooks({
hasHooks,
Wrap,
isShallow,
}) {
describeIf(hasHooks, 'hooks: custom', () => {
describe('custom hook : useCounter', () => {
function useCounter({ initialCount = 0, step = 1 } = {}) {
const [count, setCount] = useState(initialCount);
const increment = () => setCount(c => c + step);
const decrement = () => setCount(c => c - step);
return { count, increment, decrement };
}
// testing custom hooks with renderProps
// may be we can think of adding in utils
// will be repeated
const Counter = ({ children, ...rest }) => children(useCounter(rest));

function setup(props) {
const returnVal = {};
Wrap(
<Counter {...props}>
{(val) => {
Object.assign(returnVal, val);
return null;
}}
</Counter>,
);
return returnVal;
}

it('useCounter', () => {
const counterData = setup();
counterData.increment();
expect(counterData).to.have.property('count', 1);
counterData.decrement();
expect(counterData).to.have.property('count', 0);
});

it('useCounter with initialCount', () => {
const counterData = setup({ initialCount: 2 });
counterData.increment();
expect(counterData).to.have.property('count', 3);
counterData.decrement();
expect(counterData).to.have.property('count', 2);
});

it('useCounter with step', () => {
const counterData = setup({ step: 2 });
counterData.increment();
expect(counterData).to.have.property('count', 2);
counterData.decrement();
expect(counterData).to.have.property('count', 0);
});

it('useCounter with step and initialCount', () => {
const counterData = setup({ step: 2, initialCount: 5 });
counterData.increment();
expect(counterData).to.have.property('count', 7);
counterData.decrement();
expect(counterData).to.have.property('count', 5);
});
});

// todo: enable shallow when useEffect works in the shallow renderer. see https://github.com/facebook/react/issues/15275
describeIf(!isShallow, 'custom hook: formInput invoke props', () => {
function useFormInput(initialValue = '') {
const [value, setValue] = useState(initialValue);

return {
value,
onChange(e) {
setValue(e.target.value);
},
};
}

function Input(props) {
return (
<div>
<input {...props} />
</div>
);
}

function ControlledInputWithEnhancedInput({ searchSomething }) {
const search = useFormInput();

useEffect(
() => {
searchSomething(search.value);
},
[search.value],
);

return <Input {...search} />;
}

function ControlledInputWithNativeInput({ searchSomething }) {
const search = useFormInput();

useEffect(
() => {
searchSomething(search.value);
},
[search.value],
);

return <input {...search} />;
}

it('work with native input', () => {
const spy = sinon.spy();
const wrapper = Wrap(<ControlledInputWithNativeInput searchSomething={spy} />);
wrapper.find('input').invoke('onChange')({ target: { value: 'foo' } });

expect(spy.withArgs('foo')).to.have.property('callCount', 1);
});

it('work with custom wrapped Input', () => {
const spy = sinon.spy();
const wrapper = Wrap(<ControlledInputWithEnhancedInput searchSomething={spy} />);
const input = wrapper.find('Input');
input.invoke('onChange')({ target: { value: 'foo' } });
expect(spy.withArgs('foo')).to.have.property('callCount', 1);
});

it('work with custom wrapped input', () => {
const spy = sinon.spy();
const wrapper = Wrap(<ControlledInputWithEnhancedInput searchSomething={spy} />);
const input = wrapper.find('input');
input.invoke('onChange')({ target: { value: 'foo' } });
expect(spy.withArgs('foo')).to.have.property('callCount', 1);
});
});
});
}
122 changes: 122 additions & 0 deletions packages/enzyme-test-suite/test/shared/hooks/useContext.jsx
@@ -0,0 +1,122 @@
import React from 'react';
import { expect } from 'chai';

import {
describeIf,
itIf,
} from '../../_helpers';

import {
useContext,
useState,
createContext,
} from '../../_helpers/react-compat';

export default function describeUseContext({
hasHooks,
Wrap,
isShallow,
}) {
describeIf(hasHooks, 'hooks: useContext', () => {
describe('simple example', () => {
const initialTitle = 'initialTitle';
const TitleContext = createContext && createContext(initialTitle);

function UiComponent() {
const title = useContext(TitleContext);
return (
<div>
{title}
</div>
);
}

const customTitle = 'CustomTitle';

function App() {
return (
<TitleContext.Provider value={customTitle}>
<UiComponent />
</TitleContext.Provider>
);
}

it('render ui component with initial context value', () => {
const wrapper = Wrap(<UiComponent />);
expect(wrapper.text()).to.equal(initialTitle);
});

// TODO: useContext: enable when shallow dive supports createContext
itIf(!isShallow, 'render ui component with value from outer provider', () => {
const wrapper = Wrap(<App />);
const subWrapper = isShallow ? wrapper.dive().dive() : wrapper;
expect(subWrapper.text()).to.equal(customTitle);
});
});

// TODO: useContext: enable when shallow dive supports createContext
describeIf(!isShallow, 'useContext: with Setting', () => {
const initialState = 10;
const context = createContext && createContext(null);

function MyGrandChild() {
const myContextVal = useContext(context);

const increment = () => {
myContextVal.setState(myContextVal.state + 1);
};

return (
<div>
<button type="button" onClick={increment}>increment</button>
<span className="grandChildState">
{myContextVal.state}
</span>
</div>
);
}

function MyChild() {
return (
<div>
<MyGrandChild />
</div>
);
}

function App() {
const [state, setState] = useState(initialState);

return (
<context.Provider value={{ state, setState }}>
<div>
<MyChild />
</div>
</context.Provider>
);
}

it('test render, get and set context value ', () => {
const wrapper = Wrap(<App />);

function getChild() {
const child = wrapper.find(MyChild);
return isShallow ? child.dive() : child;
}
function getGrandChild() {
const grandchild = getChild().find(MyGrandChild);
return isShallow ? grandchild.dive() : grandchild;
}
expect(getGrandChild().find('.grandChildState').debug()).to.equal(`<span className="grandChildState">
${String(initialState)}
</span>`);

getGrandChild().find('button').props().onClick();
wrapper.update();
expect(getGrandChild().find('.grandChildState').debug()).to.equal(`<span className="grandChildState">
${String(initialState + 1)}
</span>`);
});
});
});
}

0 comments on commit 081554e

Please sign in to comment.