I'm using nextJs and jest enzyme for testing of component. I have a component file like below, where I get items array from props and loop to it calling setRef inside useEffect.
[itemRefs, setItemRefs] = React.useState([]);
setRef = () => {
const refArr = [];
items.forEach(item => {
const setItemRef = RefItem => {
refArr.push(RefItem)
}
if(item && item.rendItem){
item.rendItem = item.rendItem.bind(null, setItemRef);
}
}
setItemRefs(refArr);
}
React.useEffect(() => {
setRef();
},[])
My test file is like below :
const items = [
{
original: 'mock-picture-link',
thumbnail: 'mock-thumb-link',
renderItem: jest.fn().mockReturnValue({ props: { children: {} } })
}
];
beforeEach(() => {
setItemRefs = jest.fn(),
jest.spyOn(React, 'useEffect').mockImplementationOnce(fn => fn());
})
it('mocks item references', ()=>{
jest.spyOn(React, 'useState').mockImplementationOnce(() => [items, setItemRefs]);
wrapper = shallow(<Component />).dive();
expect(setItemRefs).toHaveBeenCalledWith(items);
})
The test case fails with expected as items array BUT received a blank array. The console.log inside if(item && item.rendItem) works but console.log inside const setItemRef = RefItem => {... doesn't work I doubt the .bind is not getting mocked in jest.
Any help would be fine.
Related
I am trying to set up store in React using immer and borrowed the code from another project. However it doesn't work for me. Could be that immer version on borrowed project is lower than it is now.
Store looks like this:
const initialState: MyState = {
search: "No results yet"
}
const useValue = () => useState(initialState);
const { Provider, useTrackedState, useUpdate: useSetState } = createContainer(
useValue
);
const useSetDraft = () => {
const setState = useSetState();
return useCallback(
(draftUpdater: MyState) => {
setState(produce(draftUpdater));
},
[setState]
);
};
export { Provider, useTrackedState, useSetDraft, initialState }
and hook that i use to mutate state is:
const UseSetSearch = () => {
const setDraft = useSetDraft();
return useCallback((searchString: string) => {
setDraft((draft: MyState) => {
draft.search = searchString
return draft
})
}, [setDraft]);
}
With the store, I get error
Type 'MyState' provides no match for the signature '(state: WritableDraft): ValidRecipeReturnType'
and hook error is
Value of type '(draft: MyState) => MyState' has no properties in common with type 'MyState'. Did you mean to call it?
Store error is fixed when I do:
const useSetDraft = () => {
const setState = useSetState();
return useCallback(
(draftUpdater: MyState) => {
setState(produce(draftUpdater, draft => {
}));
},
[setState]
);
};
but hook error remains. What am I missing?
const DomNodeData = () => {
useEffect(() => {
const domNode = document.getElementById('visitDate')
if (domNode) {
//do something
// this needs to be tested
}
}, [])
return (
<div id="visitDate">Data</div>
)
}
describe('DemoData', () => {
it('Render dom node', () => {
render(<DomNodeData />)
})
})
After rendering the component in the test case, I cannot get the dom node, it's null. How can this be implemented in test?
I have the following react hook which brings focus to a given ref and on unmount returns the focus to the previously focused element.
export default function useFocusOnElement(elementRef: React.RefObject<HTMLHeadingElement>) {
const documentExists = typeof document !== 'undefined';
const [previouslyFocusedEl] = useState(documentExists && (document.activeElement as HTMLElement));
useEffect(() => {
if (documentExists) {
elementRef.current?.focus();
}
return () => {
if (previouslyFocusedEl) {
previouslyFocusedEl?.focus();
}
};
}, []);
}
Here is the test I wrote for it.
/**
* #jest-environment jsdom
*/
describe('useFocusOnElement', () => {
let ref: React.RefObject<HTMLDivElement>;
let focusMock: jest.SpyInstance;
beforeEach(() => {
ref = { current: document.createElement('div') } as React.RefObject<HTMLDivElement>;
focusMock = jest.spyOn(ref.current as HTMLDivElement, 'focus');
});
it('will call focus on passed ref after mount ', () => {
expect(focusMock).not.toHaveBeenCalled();
renderHook(() => useFocusOnElement(ref));
expect(focusMock).toHaveBeenCalled();
});
});
I would like to also test for the case where document is undefined as we also do SSR. In the hook I am checking for the existence of document and I would like to test for both cases.
JSDOM included document so I feel I'd need to remove it and some how catch an error in my test?
First of all, to simulate document as undefined, you should mock it like:
jest
.spyOn(global as any, 'document', 'get')
.mockImplementationOnce(() => undefined);
But to this work in your test, you will need to set spyOn inside renderHook because looks like it also makes use of document internally, and if you set spyOn before it, you will get an error.
Working test example:
it('will NOT call focus on passed ref after mount', () => {
expect(focusMock).not.toHaveBeenCalled();
renderHook(() => {
jest
.spyOn(global as any, 'document', 'get')
.mockImplementationOnce(() => undefined);
useFocusOnElement(ref);
});
expect(focusMock).not.toHaveBeenCalled();
});
You should be able to do this by creating a second test file with a node environment:
/**
* #jest-environment node
*/
describe('useFocusOnElement server-side', () => {
...
});
I ended up using wrapWithGlobal and wrapWithOverride from https://github.com/airbnb/jest-wrap.
describe('useFocusOnElement', () => {
let ref: React.RefObject<HTMLDivElement>;
let focusMock: jest.SpyInstance;
let activeElMock: unknown;
let activeEl: HTMLDivElement;
beforeEach(() => {
const { window } = new JSDOM();
global.document = window.document;
activeEl = document.createElement('div');
ref = { current: document.createElement('div') };
focusMock = jest.spyOn(ref.current as HTMLDivElement, 'focus');
activeElMock = jest.spyOn(activeEl, 'focus');
});
wrapWithOverride(
() => document,
'activeElement',
() => activeEl,
);
describe('when document present', () => {
it('will focus on passed ref after mount and will focus on previously active element on unmount', () => {
const hook = renderHook(() => useFocusOnElement(ref));
expect(focusMock).toHaveBeenCalled();
hook.unmount();
expect(activeElMock).toHaveBeenCalled();
});
});
describe('when no document present', () => {
wrapWithGlobal('document', () => undefined);
it('will not call focus on passed ref after mount nor on previously active element on unmount', () => {
const hook = renderHook(() => useFocusOnElement(ref));
expect(focusMock).not.toHaveBeenCalled();
hook.unmount();
expect(activeElMock).not.toHaveBeenCalled();
});
});
});
I am emplying an useRef to fix focus to button next to input when change of that input is detected:
const handleUserEshopChange = (eshopId: ID) => {
setEshopIdValue(eshopId)
setEshopNameValue('')
focusRef.current.focus()
...
}
I want to test that the focus has been affixed but document.activeElement is just an empty object:
test('onNewEshopCreate is handled correctly', () => {
const mockOnUserEshopSelect = jest.fn()
const mockOnNewEshopCreate = jest.fn()
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: { focus } })
const eshopStep = mount(
<EshopStep
...
/>
, { attachTo: document.body })
act(() => {
eshopStep.find('ForwardRef(AutocompleteInput)').simulate('change', '...')
eshopStep.find('ForwardRef(ContinueButton)').at(1).simulate('click')
})
expect(mockOnNewEshopCreate).toBeCalledTimes(1)
expect(mockOnNewEshopCreate).toBeCalledWith('...')
expect(mockOnUserEshopSelect).toBeCalledTimes(0)
expect(document.activeElement).toBe(eshopStep.find('.button'))
})
Test fails on the last line, as it expects only an ampty object ( {} ). Why is the activeElement empty?
I want to isolate the test to a targeted useState.
Lets say I have 3 useStates, of which some are in my component and some are in children components in this testcase.
Currently this logs for 3 different useStates. How to target the one I want. Lets say its called setMovies.
const createMockUseState = <T extends {}>() => {
type TSetState = Dispatch<SetStateAction<T>>;
const setState: TSetState = jest.fn((prop) => {
// if setMovies ???
console.log('jest - spy mock = ', prop);
});
type TmockUseState = (prop: T) => [T, TSetState];
const mockUseState: TmockUseState = (prop) => [prop, setState];
const spyUseState = jest.spyOn(React, 'useState') as jest.SpyInstance<[T, TSetState]>;
spyUseState.mockImplementation(mockUseState);
};
interface Props {
propertyToTest: boolean
};
describe('Search Movies', () => {
describe('Onload - do first search()', () => {
beforeAll(async () => {
createMockUseState<PROPS>();
wrapper = mount(
<ProviderMovies>
<SearchMovies />
</ProviderMovies>
);
await new Promise((resolve) => setImmediate(resolve));
await act(
() =>
new Promise<void>((resolve) => {
resolve();
})
);
});
});
});
as we know react hooks depends on each initialization position. And for example if you have 3 hooks inside your component and you want to mock the 2-nd, you should mock 1 and 2 with necessary data.
Something like this
//mock for test file
jest.mock(useState); // you should mock here useState from React
//mocks for each it block
const useMockHook = jest.fn(...);
jest.spyOn(React, 'useState').mockReturnValueOnce(useMockHook);
expect(useMockHook).toHaveBeenCalled();
// after that you can check whatever you need