dom-testing-library version: 2.7.0react version: 16.4.1redux-form version: 7.4.2node version: 8.11.3npm (or yarn) version: 6.1.0import React from "react";
import { renderWithRedux, generate } from "test-utils";
import { cleanup, fireEvent } from "react-testing-library";
import LoginFormContainer from "../LoginFormContainer";
afterEach(cleanup);
describe("LoginFormContainer", () => {
test("Submits Login with email and password", () => {
//Arrange--------------
const fakeUser = generate.loginForm();
let submitLogin = jest.fn();
const props = { submitLogin };
const { getByText, getByPlaceholderText } = renderWithRedux(
<LoginFormContainer {...props} />
);
const emailNode = getByPlaceholderText("E-mail address");
const passwordNode = getByPlaceholderText("Password");
//Act--------------
emailNode.value = fakeUser.email;
^^^^^ [ts] Property 'value' does not exist on type 'HTMLElement'.
passwordNode.value = fakeUser.password;
^^^^^ [ts] Property 'value' does not exist on type 'HTMLElement'.
fireEvent.change(emailNode);
fireEvent.change(passwordNode);
fireEvent.click(getByText("Login"));
//Assert--------------
expect(submitLogin).toHaveBeenCalledTimes(1);
expect(submitLogin).toHaveBeenCalledWith(fakeUser);
});
});
renderWithRedux is the same as the examples from react-testing-library
import React from "react";
import { createStore } from "redux";
import { Provider } from "react-redux";
import { render } from "react-testing-library";
import reducer from "../services/reducer";
export default function renderWithRedux(
ui: React.ReactNode,
{ initialState, store = createStore(reducer, initialState) } = {}
) {
return {
...render(<Provider store={store}>{ui}</Provider>),
// adding `store` to the returned utilities to allow us
// to reference it in our tests (just try to avoid using
// this to test implementation details).
store
};
}
I am using redux-form for my forms.
I want to:
submitLogin is firedreact-testing-library: fireEvent If you want to trigger the
onChangehandler of a controlled component with a differentevent.target.value, sending value througheventPropertieswon't work like it does withSimulate.
You need to change the element'svalueproperty, then usefireEventto fire achangeDOM event.
I get TypeScript errors from the returned Nodes from getByPlaceholderText, because it returns a type HTMLElement.
const emailNode = getByPlaceholderText("E-mail address");
const passwordNode = getByPlaceholderText("Password");
emailNode.value = fakeUser.email;
^^^^^ [ts] Property 'value' does not exist on type 'HTMLElement'.
passwordNode.value = fakeUser.password;
^^^^^ [ts] Property 'value' does not exist on type 'HTMLElement'.
I'm not very well versed with TypeScript, but I'm wondering if updating the type definition for queries.d.ts, so that type GetByAttribute returns an intersection of HTMLElement & HTMLInputElement makes sense?
// queries.d.ts
export type GetByAttribute = (
container: HTMLElement,
id: Matcher,
options?: MatcherOptions,
) => HTMLElement & HTMLInputElement
^^^^^^^^^^^^^^^^^ Add intersection type?
export const getByPlaceholderText: GetByAttribute
In the meantime, I'm just typecasting:
const emailNode = getByPlaceholderText("E-mail address") as HTMLInputElement;
const passwordNode = getByPlaceholderText("Password") as HTMLInputElement;
Would that support a textarea as well? I'm not sure what other elements support a placeholder, but any elements that support a placeholder should be what this query should be able to return.
Also, thank you for the detailed issue! Hopefully one of our TS maintainers can jump in and help with this one :)
I鈥檒l test it out on textarea and other elements that have placeholder.
If it also works, I鈥檒l work on a PR!
Sent with GitHawk
This is a problem with all the get and queries where the returned element is unknown, this could be a solution:
// add generics in all the get and queries
export type GetByAttribute = <T extends HTMLElement>(
container: HTMLElement,
id: Matcher,
options?: MatcherOptions,
) => T
// in your code
getByPlaceholderText<HTMLInputElement>("Password")
That sounds fine with me. I don't maintain the typescript typings so anyone who wants to help maintain them is welcome to do so.
This is a problem with all the get and queries where the returned element is unknown
That's true.
But I think the existing typings are right, type definition should not be changed.
The element type is actually unknown. The return value is generic, HTMLElement. The callers have to narrow down the types themselves after checking which type of element they actually got from the query. It's an assumption that they got HTMLInputElement which has to be verified in runtime, they could've gotten any other HTMLElement (like a textarea), and there's no way to define the return type statically depending on the queried DOM structure that is dynamic.
I see.
I don't have any counter arguments to @sompylasar
But I think the existing typings are right, type definition should not be changed.
@sompylasar, could you provide some feedback regarding my method of typecasting to satisfy TypeScript?
Is that they way you would go about it?
const emailNode = getByPlaceholderText("E-mail address") as HTMLInputElement;
const passwordNode = getByPlaceholderText("Password") as HTMLInputElement;
I just had to implement that same fix - my compiler refused to pass the input element as having a value property unless I cast it as HTMLInputElement.
@duncanleung @ckhicks
You don't need to cast to HTMLInputElement, you need to check that whatever object you got from getByPlaceholderText is in fact an HTMLInputElement and not something else. That the element will always be an HTMLInputElement is an assumption that may be broken, typecasting just hides that and begs for runtime errors that may have been prevented.
const emailNode = getByPlaceholderText("E-mail address");
const passwordNode = getByPlaceholderText("Password");
if (emailNode instanceof HTMLInputElement && passwordNode instanceof HTMLInputElement) {
// Do something with emailNode, passwordNode; TypeScript should narrow their types to be HTMLInputElement.
} else {
assert(false, 'expected emailNode and passwordNode to be HTMLInputElement');
}
Thanks for your time and the advice!
I'll use that approach in my test.
Most helpful comment
@duncanleung @ckhicks
You don't need to cast to
HTMLInputElement, you need to check that whatever object you got fromgetByPlaceholderTextis in fact anHTMLInputElementand not something else. That the element will always be anHTMLInputElementis an assumption that may be broken, typecasting just hides that and begs for runtime errors that may have been prevented.