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?
import { screen } from "@testing-library/react"; ... // get HTML DOM element const spanElement = await screen. queryByTestId('test-span'); // OR // const spanElement = await screen. querySelector('test-span'); // get value from HTML DOM element const spanElementValue = spanElement. getAttribute('value'); ...
The onChange event handler is used by <input type='text'> jsx, so you should use type(element, text, [options]) to writes text inside an <input> or a <textarea> . selectOptions(element, values) is used for selecting the specified option(s) of a <select> or a <select multiple> element.
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...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With