userEvent.type doesn't work well when a value is formatted after the onChange is fired
See original GitHub issue@testing-library/user-event
version: 12.0.2- Testing Framework and version: jest (24.9.0 locally and whatever version CodeSandbox uses)
- DOM Environment:
@testing-library/react
’s render function, so jsdom?
React component logic
export default function App() {
const [rawPhone, setRawPhone] = useState('');
const [formattedPhone, setFormattedPhone] = useState('');
return (
<div className="App">
<form>
<label htmlFor="raw-phone">Raw Phone: </label>
<input id="raw-phone" type="text" value={rawPhone} onChange={event => setRawPhone(event.target.value)} />
<label htmlFor="formatted-phone">Formatted Phone: </label>
<input id="formatted-phone" type="text" value={formattedPhone} onChange={event => setFormattedPhone(formatPhone(event.target.value)) } />
</form>
</div>
);
}
Format function
export function formatPhone(unformattedPhone?: string): string {
if (unformattedPhone == null) return '';
// Remove all formatting from input
let matchedValue = unformattedPhone.replace(/[^0-9]*/g, '').match(/\d*/g);
const cleanedValue = matchedValue ? matchedValue.join('') : '';
// group between areaCode, firstGroup, and secondGroup
matchedValue = cleanedValue.match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
const [areaCode, firstGroup, secondGroup] = matchedValue ? matchedValue.slice(1) : ['', '', ''];
// initialize phoneNumber
let phoneNumber = '';
// begin with '(' after any digit input
if (areaCode !== '') phoneNumber = `(${areaCode}`;
// end area code with ')' if there are more than 3 digits (at least 1 digit in firstGroup)
if (firstGroup !== '') phoneNumber = `${phoneNumber}) ${firstGroup}`;
// add '-' if there are more than 6 digits (at least 1 digit in secondGroup)
if (secondGroup !== '') phoneNumber = `${phoneNumber}-${secondGroup}`;
return phoneNumber;
}
Tests
test('Raw phone should set the value to match excatly what is typed', async () => {
render(<App />);
userEvent.type(screen.getByLabelText(/Raw Phone/i), '1234567890');
expect(screen.getByLabelText(/Raw Phone/i)).toHaveValue('1234567890');
});
test('Formatted phone should format the value as it is typed', async () => {
render(<App />);
userEvent.type(screen.getByLabelText(/Formatted Phone/i), '1234567890');
expect(screen.getByLabelText(/Formatted Phone/i)).toHaveValue('(123) 456-7890');
});
What you did:
I have a field where we collect a phone number and format it as the user types. To do so, we format the value onChange before we call the setState
function from the useState
hook`.
What happened:
When attempting to test this by calling userEvent.type(..., '1234567890')
the value was being set to (098) 765-4321 instead of (123) 456-7890.
Reproduction repository:
https://codesandbox.io/s/usereventtype-reverses-value-mgi2n?file=/src/App.spec.tsx
Problem description:
This was extremely confusing at first and led me to do a lot of digging in the source code of userEvent.type(...)
. Once I did so for a while I came across the idea that it may be an issue with the selected location returning to the beginning of the input. I think it may have something to do with the newValue being different than it expected.
Suggested solution:
I don’t know that I have a suggested solution, as I don’t have a complete grasp over the source code and the cause of the specific problem. However, I’m more than happy to help discuss options and take a crack at implementing a fix with some guidance.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:18 (17 by maintainers)
Top GitHub Comments
@terraelise @mickcastle Please don’t add noise to the issue board. File an issue with a reproducible example.
Also try upgrading to the latest version. The issue above has been resolved. There might be another issue with
13.5.0
that might or might not be already resolved.I am experiencing this issue with 13.5.0, had to downgrade to 12.0.4 to solve it.