Simulate change for textarea Jest test - reactjs

I have the following component:
render() {
return (
<textarea onChange={this.handlechange} value="initial value" />
)
}
handlechange = (e) => {
console.log(e.currentTarget.value);
}
and the corresponding test that's supposed to check if on change fired correctly or not:
const TEST_PROPS = {
OnChange: jest.fn()
}
it("Fires on change correctly", () => {
const textArea = enzyme.mount(<TextArea {...TEST_PROPS} />);
jest.resetAllMocks();
expect(textArea.find("textarea").simulate("change"));
expect(TEST_PROPS.OnChange).toHaveBeenCalledTimes(1);
expect(TEST_PROPS.OnChange).toHaveBeenLastCalledWith(//what should go here?//);
});
I want to pass in the value of the new target.value once the onchange is fired to the toHaveBeenLastCalledWith function. How can I do this?

simulate event accepts a event obj as a 2nd arg, which you can use it in your 2nd assertion.
const TEST_PROPS = {
OnChange: jest.fn()
}
it("Fires on change correctly", () => {
const textArea = enzyme.mount(<TextArea {...TEST_PROPS} />);
const event = { target: { value: "sometext" } };
jest.resetAllMocks();
expect(textArea.find("textarea").simulate("change", event));
expect(TEST_PROPS.OnChange).toHaveBeenCalledTimes(1);
expect(TEST_PROPS.OnChange).toHaveBeenLastCalledWith(event);
});

Related

(Jest/Enzyme/React) How to assert that throttled function inside change handler has been called?

I need to assert that SearchInputActions.onSearchActivated(value) is called when something is written in an input. It is a callback which is in change handler handleChange. I've been trying to create to mocks - one for handleChange and one for search but it did not work either. I am using jest and enzyme for tests.
const SearchInput = () => {
const search = throttle(event => {
const value = event.target.value;
SearchInputActions.onSearchActivated(value);
});
const handleChange = event => {
event.persist();
search(event);
};
return (
<div>
<SomeChildComponent />
<input type="text" onChange={handleChange} />
</div>
)
}
Test:
it('should dispatch search action', async () => {
const tree = mount(<SearchInput />);
const spySearch = jest.spyOn(SearchInputActions, 'onSearchActivated');
SearchInputActions.onSearchActivated.mockImplementation(() => {})
tree.find('input').simulate('change', {target: value: 'test'}});
expect(spySearch).toBeCalled();
}
I figured it out: Because the callback function was throttled (lodash throttle) I needed to add jest.useFakeTimers(); Final code looks like this:
jest.useFakeTimers();
it('should dispatch search action', async () => {
const tree = mount(<SearchInput />);
const spySearch = jest.spyOn(SearchInputActions, 'onSearchActivated');
SearchInputActions.onSearchActivated.mockImplementation(() => {})
tree.find('input').simulate('change', {target: value: 'test'}});
expect(spySearch).not.toBeCalled();
jest.runAllTimers();
expect(spySearch).toBeCalled();
SearchInputActions.onSearchActivated.mockRestore();
}

How to test for document being undefined with RTL?

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();
});
});
});

Testing functional component in React onChange

I am trying to test a functional component. And the goal is to evaluate that the value change correctly.
I have only managed to carry out the test checking that it renders. But I can't find a way to pass the props to him
InputPassword
export default function InputPassword({ password, SetPassword }: any) {
return (
<input
type="password"
placeholder="password"
value={password ?? ''}
onChange={(event) => SetPassword(event.target.value)}
/>
);
}
Test:
test('Login InputPassword', () => {
render(<InputPassword />);
const username_input = screen.queryByPlaceholderText(/password/i);
});
Update final code
test('Login InputPassword', async () => {
const fakePass = '123';
const fakeCb = jest.fn();
render(<InputPassword password={fakePass} SetPassword={fakeCb} />);
const usernameInput = screen.queryByPlaceholderText(/password/i);
expect(usernameInput).toHaveValue(fakePass);
fireEvent.change(usernameInput, { target: { value: '000' } });
expect(fakeCb).toHaveBeenCalled();
});
Inside the render function you can pass props to the component just like you would pass props anywhere else.
test('Login InputPassword', () => {
render(<InputPassword password="123" />);
const username_input = screen.queryByPlaceholderText(/password/i);
});
Based on your comment:
test("Login InputPassword", async () => {
const fakePass = "123";
const fakeCb = jest.fn();
render(<InputPassword password={fakePass} setPassword={fakeCb} />);
const usernameInput = screen.queryByPlaceholderText(/password/i);
expect(usernameInput).toHaveValue(fakePass);
await userEvent.type(username_input, "changed");
expect(usernameInput).toHaveValue("changed");
expect(fakeCb).toHaveBeenCalledTimes(7);
});
On mount the input displays the password that is given to it via props. Then after the user provides a new password which calls the handler accordingly and the input's value is also updated.

Why is Enzyme document.activeElement an empty object?

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?

React unit test with enzyme, test onBlur functionality

I have a component with functionality which validates input element onblur and adds error class if validation fails:
TableRows = (props )=>(
return <input class="inputElement" onBlur="this.validate()" />
)
validate function is as follows:
validate = async ({ target }: ChangeEvent<HTMLInputElement>) => {
try {
const element = target;
this.setState({ loading: true });
const { value } = target;
const match = value.match(/^\d+(?:\.\d{0,2})?$/g);
if (!match || match.length === 0) {
element.className += ' inputError';
} else {
element.className = target.className.replace(' inputError', '');
}
const { data: updatedValues } = await sendforSaving({inputValue: value});
this.setState({ newValues: data });
} finally {
this.setState({ loading: false });
}
};
I am trying to write a unit test with enzyme as follows:
it('should add error class on invalid input onblur', () => {
const mockVal4Test = {
localCurrency: 'USD',
value: '20.02.1',
} as any;
component = shallow(
<TableRows {...defaultProps} initialValues={mockVal4Test} currencyType={CurrencyType.LOCAL} />
);
const myInput = component.find('.inputElement');
myInput.simulate('blur');
expect(saleTarget.hasClass('inputError')).toBe(true);
});
I get the myInput element but after simulating blur I am expecting the 'validate' function to be called and error class "inputError" to be added to the element.
I used a mock event for blur event to pass to simulate. And later the same mock event is used to check the changes, as the blur event changes the passed event object. Here is my solution.
it('should add error class on invalid input for StoreTarget input on blur', () => {
component = shallow(
<TableRows {...defaultProps} initialValues={mockVal4Test} currencyType={CurrencyType.LOCAL} />
)
const mockedEvent = {
target: {
value: '1.2.1.2',
className:'inputClass'
}
} as any;
const myInput = component.find('. inputElement');
myInput.simulate('blur', mockedEvent );
expect(mockedEvent.target.className.includes('inputError')).toBe(true);
});

Resources