Open lukescott opened 5 years ago
I solved this issue myself by using @testing-library/user-event
's type
method, rather than fireEvent.change
.
@pjaws, how did you solve this issue? userEvent.type
isn't changing the value of the masked input for me.
@Smona for this particular issue, userEvent.type
worked for me; however, I had other issues with this lib that lead me to switch to react-text-mask, which solved all of them. Hope that helps.
Simplest solution is to mock whole library with simple input
jest.mock('react-input-mask', () => ({ value, onChange, id, autoFocus = false }) => (
<input id={id} type="text" name="primary_contact_phone" value={value} onChange={event => onChange(event)} />
));
That's not a good solution at all. This is not something you want to mock.
On Mon, Mar 9, 2020 at 1:19 AM Kamil Woźny notifications@github.com wrote:
Simplest solution is to mock whole library with simple input jest.mock('react-input-mask', () => ({ value, onChange, id, autoFocus = false }) => ( <input id={id} type="text" name="phone" value={value} onChange={event => onChange(event)} /> ));
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sanniassin/react-input-mask/issues/174?email_source=notifications&email_token=AMVRRISTW5Z6EJUS73FQ5YLRGSQ7XA5CNFSM4HMT6IH2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEOGC7OY#issuecomment-596389819, or unsubscribe https://github.com/notifications/unsubscribe-auth/AMVRRIVFZB2FIGV2I4GLCR3RGSQ7XANCNFSM4HMT6IHQ .
This might have been fixed in a recent update to jsdom: https://github.com/jsdom/jsdom/issues/2787. I saw this mentioned as part of https://github.com/testing-library/react-testing-library/issues/247.
I'm using create-react-app
+ @testing-library/user-event
with the type()
but doesn't work for me as well. I also updated react-scripts
to be ^3.4.1
.
I found a problem in another library react-imask
, solved it using change event:
test('change on react-imask component', async () => {
const { getByTestId } = render(<Step1 />);
const inputRealState: any = getByTestId('realStateValue');
expect(inputRealState.value).toBe('');
await fireEvent.change(inputRealState, { target: { value: '3' } });
expect(inputRealState.value).toBe('3');
});
Maybe it can be useful to react-input-mask
too.
I found a problem in another library
react-imask
, solved it using change event:test('change on react-imask component', async () => { const { getByTestId } = render(<Step1 />); const inputRealState: any = getByTestId('realStateValue'); expect(inputRealState.value).toBe(''); await fireEvent.change(inputRealState, { target: { value: '3' } }); expect(inputRealState.value).toBe('3'); });
Maybe it can be useful to
react-input-mask
too.
How did you insert [data-testid] into your InputMask tag? I'm trying to put [data-testid] and [inputProps = {{"data-testid": ...}}] but nothing works.
Any update on this?
It seems that onChange
is triggered however the input value is not updated.
I tried using fireEvent and also userEvent but no one of those works for me. :/
Can someone help with that?
My solution is to use another lib
@iagopiimenta which one are you using?
Any updates on this issue?
For now, I followed the @lukescott solution
import TestUtils from 'react-dom/test-utils';
const changeInputMaskValue = (element, value) => {
element.value = value;
element.selectionStart = element.selectionEnd = value.length;
TestUtils.Simulate.change(element);
};
Something i've noticed is that if there are any "blur" events in the test the "change" event does not work but for tests that don't have any "blur" events, we are able to use the "change" event.
I had this problem too. Moved to https://nosir.github.io/cleave.js/ :/
Any update on this?
It seems that
onChange
is triggered however the input value is not updated.I tried using fireEvent and also userEvent but no one of those works for me. :/
Can someone help with that?
@iagopiimenta which one are you using?
Any news?? A component that cannot be tested is something really sad
@afucher I am not sure what do you mean by "cannot be tested", here I leave you how to test it, or at least it works for me.
import userEvent from '@testing-library/user-event';
import TestUtils from 'react-dom/test-utils';
function changeInputMaskValue(element, value) {
element.value = value;
element.selectionStart = element.selectionEnd = value.length;
TestUtils.Simulate.change(element);
};
it('example test', async () => {
render(
<MyComponent
amount="100.00"
/>,
);
act(() => {
// Both lines of codes are required
userEvent.type(screen.getByLabelText('Amount'), '300');
changeInputMaskValue(screen.getByLabelText('Amount'), '300');
});
act(() => {
// Do not move the form submitting to the previous `act`, it must be in two
// separate `act` calls.
userEvent.click(screen.getByText('Next'));
});
// You must use `findByText`
const error = await screen.findByText(/\$100.00 to redeem/);
expect(error).toBeInTheDocument();
});
Why fireEvent doesn't work? Didn't try this way, will check
My quick fix for that (using Sinon for testing):
Create function that will return native input component:
function FakePhoneInput(props): ReactElement {
return (
<label>
Phone number
<input type="tel" {...props} autoComplete="off"></input>
</label>
);
}
Later create a stub that will fake your original component (component which uses masked-input):
sandbox.stub(phoneInputComponent, 'default').callsFake((props) => FakePhoneInput(props));
In this case during tests you will create normal 'plain' input, instead of react-input-mask component.
In case if somebody also struggling from this issue, here is yet another hack, that may be useful:
const simulateInput = async (
element?: HTMLInputElement | null,
value = '',
delay = 0
): Promise<void> => {
if (!element) return
element.click()
const inner = async (rest = ''): Promise<void> => {
if (!rest) return
const { value: domValue } = element
const caretPosition = domValue.search(/[A-Z_a-z]/) // regExp to match maskPlaceholder (which default is "_"), change according to your needs
const newValue = domValue
.slice(0, caretPosition)
.concat(rest.charAt(0))
.concat(domValue.slice(caretPosition))
fireEvent.change(element, { target: { value: newValue } })
await new Promise((resolve) => setTimeout(resolve, delay))
return inner(rest.slice(1))
}
return inner(value)
}
const clearInput = (element?: HTMLInputElement | null): void => {
if (!element) return
fireEvent.change(element, { target: { value: '' } })
}
Usage inside test block:
...
const input = screen.getByTestId('input')
expect(input).toHaveValue('YYYY - mm - dd')
await simulateInput(input, '2099abcdEFG-+=/\\*,. |') // only 1998-2000 years are allowed, so '99' should also be truncated
expect(input).toHaveValue('20YY - mm - dd')
await simulateInput(input, '000229')
expect(input).toHaveValue('2000 - 02 - 29')
clearInput(input)
expect(input).toHaveValue('YYYY - mm - dd')
...
Seems that this issue related to how JSDOM maintains focus of the input (always places caret at the end of entered value), so, all chars entered with userEvent.type are placed outside the mask and therefofe truncated by internal logic of react-input-mask.
When using fireEvent.change, everything works as expected (thanks to internal logic of react-input-mask, which fills chars that match mask into placeholders), except the case with dynamic mask generation as shown in the example above with 1998-2000 years. If i call single fireEvent.change('2099'), the input value will be '2099 - mm - dd' instead of '20yy - mm - dd' which is wrong in mentioned case.
So, the hack purpose is to fire change events sequentially for a complete input value with only one char replacement at time (i.e. simulating typing symbols one by one), next typed symbol will replace the first maskPlaceholder char.
Promises can be omitted from simulateInput (as well as setTimeout call), and then it will be possible to use it without await keyword.
Hope, it'll help somebody.
P.S. The same idea can be used to simulate backspace removal one by one. react-input-mask version 3.0.0-alpha.2
I found a problem in another library
react-imask
, solved it using change event:test('change on react-imask component', async () => { const { getByTestId } = render(<Step1 />); const inputRealState: any = getByTestId('realStateValue'); expect(inputRealState.value).toBe(''); await fireEvent.change(inputRealState, { target: { value: '3' } }); expect(inputRealState.value).toBe('3'); });
Maybe it can be useful to
react-input-mask
too.
This solved the problem for me.
Yep, this one seem to work, though a bit quirky:
interface IElement extends Element {
value: string
selectionStart: number
selectionEnd: number
}
const changeInputMaskValue = (element: IElement, value: string | any[]) => {
if (typeof value === 'string') {
element.value = value
}
element.selectionStart = element.selectionEnd = value.length
TestUtils.Simulate.change(element)
}
....
test('Goes through the entire flow - happy path', async () => {
const user = userEvent.setup()
const inputPhoneNumber = screen.getByRole('textbox', { name: /phone number/i })
await act(async () => {
user.type(inputPhoneNumber, '+48790789789')
changeInputMaskValue(inputPhoneNumber, '+48790789789')
})
})
This fails:
With:
If if I change the test to:
It works. There doesn't seem to be a way to use
fireEvent
and changeselectionStart
.It would seem if
selection
were set to the length of the value inside ofif (this.isInputAutofilled(...) {
, similar to howif (beforePasteState) {
does, it seems to work. I'm not sure what the consequences of that are though.