React Testing Library with TypeScript:设置输入值

React Testing Library with TypeScript: Set an input's value

我正在使用 TypeScript 构建一个 React 应用程序。我使用 React 测试库进行组件测试。

假设您有这样的简单形式:

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}

this video 中,Kent(库的创建者)展示了如何测试表单输入。测试看起来像这样:

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',
  })
})

问题是他是用普通的 JavaScript 做的。这样做时 TypeScript 他设置 .value 的行抛出以下错误

[ts] Property 'value' does not exist on type 'HTMLElement'.

如何使用 React 测试库通过 TypeScript 测试这一功能?您将如何设置输入的值?

该库提供的类型将 getByLabelText 的 return 值键入为类型:HTMLElement。并非所有 HTML 元素都具有 value 属性,只有 HTMLInputElement 这样的元素才具有。

getByLabelText 也没有通用类型,您可以通过它影响输出类型,因此基本上您需要将结果不安全地转换为 HTMLInputElement 类型,或者您将需要构建一个辅助函数来告诉 TypeScript 对象的类型是否正确:

  1. 不安全的转换。您真正需要做的就是将对 getByLabelText 的任何调用更新为 value 属性 的类型:

    (getByLabelText(/username/i) as HTMLInputElement).value = 'chuck';
    
  2. 类型验证。这种方法更安全一些,因为您可以提供一个类型验证函数,它会导致 TypeScript 更新类型:

    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...
    }