I'm building a React application using TypeScript. I do my component tests using React Testing Library.
Let's say you have simple form like this:
import React from 'react'
function Login({onSubmit}) {
return (
<div>
<form
onSubmit={e => {
e.preventDefault()
const {username, password} = e.target.elements
onSubmit({
username: username.value,
password: password.value,
})
}}
>
<label htmlFor="username">Username</label>
<input id="username" />
<label htmlFor="password">Password</label>
<input id="password" type="password" />
<br />
<button type="submit">Submit</button>
</form>
</div>
)
}
export {Login}
In this video Kent (the creator of the library) shows how you would test the forms input inputs. The tests would look like this:
import React from 'react'
import {renderIntoDocument, cleanup} from 'react-testing-library'
import {Login} from '../login'
afterEach(cleanup)
test('calls onSubmit with username and password', () => {
const handleSubmit = jest.fn()
const {getByLabelText, getByText} = renderIntoDocument(
<Login onSubmit={handleSubmit} />,
)
getByLabelText(/username/i).value = 'chuck'
getByLabelText(/password/i).value = 'norris'
getByText(/submit/i).click()
expect(handleSubmit).toHaveBeenCalledTimes(1)
expect(handleSubmit).toHaveBeenCalledWith({
username: 'chuck',
password: 'norris',
})
})
The problem is that he did it with plain JavaScript. When doing this with
TypeScript the lines where he sets .value
throw the following errors
[ts] Property 'value' does not exist on type 'HTMLElement'.
How would one test this functionality with TypeScript using React Testing Library? How would you set the input's values?
The typings provided by that library type the return value of getByLabelText
as type: HTMLElement
. Not all HTML elements have value
properties, only things like HTMLInputElement
do.
The getByLabelText
also has no sort of generic type through which you might be able to affect the output type, so essentially you will need to either unsafely cast the result to type HTMLInputElement
or you will need to build a helper function that tells TypeScript whether or not the object is the right type:
Unsafe cast. All you really need to do is update any calls to getByLabelText
where you expect it to be a type with a value
property to:
(getByLabelText(/username/i) as HTMLInputElement).value = 'chuck';
Type validation. This method is a bit safer as you can provide a type validation function that will cause TypeScript to update the type:
function isElementInput<T extends HTMLElement>(element: T): T is HTMLInputElement {
// Validate that element is actually an input
return element instanceof HTMLInputElement;
}
// Update your attempted value sets:
const elem = getByLabelText(/username/i);
if (isElementInput(elem)) {
elem.value = 'chuck';
} else {
// Handle failure here...
}