reactjs - Hur man testar Material UI-autocomplete med React Testing Library
Jag använder material-ui-autocomplete-komponenten och försöker testa den med react-testing-library
Komponent:
/* eslint-disable no-use-before-define */
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import React from 'react';
export default function ComboBox() {
const [autocompleteInputValue, setAutocompleteInputValue] = React.useState('');
const [isAutocompleteOpen, setIsAutocompleteOpen] = React.useState(false);
const renderInput = (params: any) => <TextField {...params} label='openOnFocus: false' variant='outlined' />;
const getTitle = (option: any) => option.title;
const handleAutocompleteInputChange = (event: any, value: string) => {
setAutocompleteInputValue(value);
};
const updateAutocompletePopper = () => {
setIsAutocompleteOpen(!isAutocompleteOpen);
};
return (
<Autocomplete
id='autocompleteSearch'
data-testid='autocomplete-search'
disableClearable={true}
renderOption={getTitle}
getOptionLabel={getTitle}
renderInput={renderInput}
options={top100Films}
clearOnEscape={true}
onInputChange={handleAutocompleteInputChange}
inputValue={autocompleteInputValue}
open={isAutocompleteOpen}
onOpen={updateAutocompletePopper}
onClose={updateAutocompletePopper}
style={{ width: 300 }}
ListboxProps={{ 'data-testid': 'list-box' }}
/>
);
}
// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
export const top100Films = [
{ title: 'The Shawshank Redemption', year: 1994 },
{ title: 'The Godfather', year: 1972 },
{ title: 'The Godfather: Part II', year: 1974 },
{ title: 'The Dark Knight', year: 2008 },
{ title: '12 Angry Men', year: 1957 },
{ title: 'Schindlers List', year: 1993 },
{ title: 'Pulp Fiction', year: 1994 },
{ title: 'The Lord of the Rings: The Return of the King', year: 2003 },
{ title: 'The Good, the Bad and the Ugly', year: 1966 },
{ title: 'Fight Club', year: 1999 },
{ title: 'The Lord of the Rings: The Fellowship of the Ring', year: 2001 },
{ title: 'Star Wars: Episode V - The Empire Strikes Back', year: 1980 },
{ title: 'Forrest Gump', year: 1994 },
{ title: 'Inception', year: 2010 },
{ title: 'The Lord of the Rings: The Two Towers', year: 2002 },
{ title: 'One Flew Over the Cuckoos Nest', year: 1975 },
{ title: 'Goodfellas', year: 1990 },
{ title: 'The Matrix', year: 1999 },
{ title: 'Seven Samurai', year: 1954 },
{ title: 'Star Wars: Episode IV - A New Hope', year: 1977 },
{ title: 'City of God', year: 2002 },
{ title: 'Se7en', year: 1995 },
{ title: 'The Silence of the Lambs', year: 1991 },
{ title: 'Its a Wonderful Life', year: 1946 },
{ title: 'Life Is Beautiful', year: 1997 },
{ title: 'The Usual Suspects', year: 1995 },
{ title: 'Léon: The Professional', year: 1994 },
{ title: 'Spirited Away', year: 2001 },
{ title: 'Saving Private Ryan', year: 1998 },
{ title: 'Once Upon a Time in the West', year: 1968 },
{ title: 'American History X', year: 1998 },
{ title: 'Interstellar', year: 2014 },
{ title: 'Casablanca', year: 1942 },
{ title: 'City Lights', year: 1931 },
{ title: 'Psycho', year: 1960 },
{ title: 'The Green Mile', year: 1999 },
{ title: 'The Intouchables', year: 2011 },
{ title: 'Modern Times', year: 1936 },
{ title: 'Raiders of the Lost Ark', year: 1981 },
{ title: 'Rear Window', year: 1954 },
{ title: 'The Pianist', year: 2002 },
{ title: 'The Departed', year: 2006 },
{ title: 'Terminator 2: Judgment Day', year: 1991 },
{ title: 'Back to the Future', year: 1985 },
{ title: 'Whiplash', year: 2014 },
{ title: 'Gladiator', year: 2000 },
{ title: 'Memento', year: 2000 },
{ title: 'The Prestige', year: 2006 },
{ title: 'The Lion King', year: 1994 },
{ title: 'Apocalypse Now', year: 1979 },
{ title: 'Alien', year: 1979 },
{ title: 'Sunset Boulevard', year: 1950 },
{
title: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb',
year: 1964,
},
{ title: 'The Great Dictator', year: 1940 },
{ title: 'Cinema Paradiso', year: 1988 },
{ title: 'The Lives of Others', year: 2006 },
{ title: 'Grave of the Fireflies', year: 1988 },
{ title: 'Paths of Glory', year: 1957 },
{ title: 'Django Unchained', year: 2012 },
{ title: 'The Shining', year: 1980 },
{ title: 'WALL·E', year: 2008 },
{ title: 'American Beauty', year: 1999 },
{ title: 'The Dark Knight Rises', year: 2012 },
{ title: 'Princess Mononoke', year: 1997 },
{ title: 'Aliens', year: 1986 },
{ title: 'Oldboy', year: 2003 },
{ title: 'Once Upon a Time in America', year: 1984 },
{ title: 'Witness for the Prosecution', year: 1957 },
{ title: 'Das Boot', year: 1981 },
{ title: 'Citizen Kane', year: 1941 },
{ title: 'North by Northwest', year: 1959 },
{ title: 'Vertigo', year: 1958 },
{ title: 'Star Wars: Episode VI - Return of the Jedi', year: 1983 },
{ title: 'Reservoir Dogs', year: 1992 },
{ title: 'Braveheart', year: 1995 },
{ title: 'M', year: 1931 },
{ title: 'Requiem for a Dream', year: 2000 },
{ title: 'Amélie', year: 2001 },
{ title: 'A Clockwork Orange', year: 1971 },
{ title: 'Like Stars on Earth', year: 2007 },
{ title: 'Taxi Driver', year: 1976 },
{ title: 'Lawrence of Arabia', year: 1962 },
{ title: 'Double Indemnity', year: 1944 },
{ title: 'Eternal Sunshine of the Spotless Mind', year: 2004 },
{ title: 'Amadeus', year: 1984 },
{ title: 'To Kill a Mockingbird', year: 1962 },
{ title: 'Toy Story 3', year: 2010 },
{ title: 'Logan', year: 2017 },
{ title: 'Full Metal Jacket', year: 1987 },
{ title: 'Dangal', year: 2016 },
{ title: 'The Sting', year: 1973 },
{ title: '2001: A Space Odyssey', year: 1968 },
{ title: 'Singin in the Rain', year: 1952 },
{ title: 'Toy Story', year: 1995 },
{ title: 'Bicycle Thieves', year: 1948 },
{ title: 'The Kid', year: 1921 },
{ title: 'Inglourious Basterds', year: 2009 },
{ title: 'Snatch', year: 2000 },
{ title: '3 Idiots', year: 2009 },
{ title: 'Monty Python and the Holy Grail', year: 1975 },
];
Beroende på det alternativ som väljs från autokompletteringen gör jag några andra saker, som att rendera en chip, en annan komponent etc. Men för att göra det enkelt testar jag initialt bara att när användaren fokuserar i inmatningsfältet visas popupen, så att jag senare kan klicka på ett alternativ i denna popup och testa att allt annat fungerar som förväntat. Jag verifierar popupen med hjälp av data-testid som jag tilldelade listboxen genom ListboxProps-prop på autokompletteringen:
Test:
import {
fireEvent,
getByRole as globalGetByRole,
getByText as globalGetByText,
render,
} from '@testing-library/react';
import React from 'react';
import ComboBox, { top100Films } from './AutoComplete';
test('that autocomplete works', async () => {
const { getByTestId, getByRole, queryByRole } = render(<ComboBox />, {});
const AutoCompleteSearch = getByTestId('autocomplete-search');
const Input = globalGetByRole(AutoCompleteSearch, 'textbox');
expect(queryByRole('listbox')).toBeNull();
fireEvent.mouseDown(Input);
const ListBox = getByRole('listbox');
expect(ListBox).toBeDefined();
const menuItem1 = globalGetByText(ListBox, top100Films[0].title);
fireEvent.click(menuItem1);
expect(queryByRole('listbox')).toBeNull();
fireEvent.mouseDown(Input);
const ListBoxAfter = getByRole('listbox');
expect(ListBoxAfter).toBeDefined();
const menuItem2 = globalGetByText(ListBoxAfter, top100Films[1].title);
fireEvent.click(menuItem2);
expect(queryByRole('listbox')).toBeNull();
});
Men detta misslyckas med: Kan inte hitta ett element med: [data-testid="list-box"]. Vad gör jag fel?
REDIGERA: Jag utlöste mouseDown på Input och kunde framgångsrikt testa att popup-fönstret öppnades. Jag använde listbox-rollen istället för ett data-testid för att verifiera att popup-fönstret hade öppnats. Samma sak kan göras med data-testid också. Sedan valde jag ett objekt från autokompletteringsalternativen och popup-fönstret stängdes. Nu försökte jag öppna popup-fönstret igen för andra gången och här misslyckas det igen. Kan inte öppna det för andra gången med mouseDown-händelsen.
Svar #1
Först och främst behöver du se till att alternativen inte är en tom array, sedan gör följande:
const autocomplete = getByTestId('autocomplete');
const input = within(autocomplete).getByRole('textbox')
autocomplete.focus()
// the value here can be any string you want, so you may also consider to
// wrapper it as a function and pass in inputValue as parameter
fireEvent.change(input, { target: { value: 'a' } })
fireEvent.keyDown(autocomplete, { key: 'ArrowDown' })
fireEvent.keyDown(autocomplete, { key: 'Enter' })
Svar #2
Eftersom listobjekten inte är ”synliga” i själva DOM behöver du använda ett annat tillvägagångssätt.
Du måste hitta autokompletterings- och indata-DOM-elementen på vilka du kommer att utlösa händelser.
Autokomplettering finns vanligtvis i DOM:en genom rollattributet, t.ex. role="combobox", men det är bäst att ge det ett unikt identifierare såsom data-testid="autocomplete"
Följande kod visar hur man testar objektval i autokomplettering:
const autocomplete = getByTestId('autocomplete');
const input = within(autocomplete).querySelector('input')
autocomplete.focus()
// assign value to input field
fireEvent.change(input, { target: { value: value } })
await wait()
// navigate to the first item in the autocomplete box
fireEvent.keyDown(autocomplete, { key: 'ArrowDown' })
await wait()
// select the first item
fireEvent.keyDown(autocomplete, { key: 'Enter' })
await wait()
// check the new value of the input field
expect(input).toHaveValue('some_value')
Du behöver ange ett värde i inmatningselementet och sedan utlösa ändringen. Därefter öppnas listboxen som tillåter att välja det första värdet genom att trycka på enter-tangenten. Det valda värdet kommer att ersätta det initiala värdet som användes för att söka/öppna autokompletteringen.
Svar #3
// make sure autocomplete reactions/results do not already exist
expect(screen.queryByText(/Loading/)).not.toBeInTheDocument()
expect(screen.queryByText(/Van Halen/)).not.toBeInTheDocument()
// fill out autocomplete
const faveBand = screen.getByLabelText(/Favorite Band/)
userEvent.type(faveBand, 'Van H')
expect(faveBand).toHaveValue('Van H')
// witness autocomplete working
expect(screen.getByText(/Loading/)).toBeInTheDocument()
// wait for response (i used an async Material-UI autocomplete)
// favebands is a data-testid attribute value in my autocomplete
// component, e.g. ListboxProps={{ 'data-testid': 'favebands' }}
await waitFor(() => getByTestId('favebands'))
// verify autocomplete items are visible
expect(screen.getByText(/Van Halen/)).toBeInTheDocument()
// click on autocomplete item
const faveBandItem = screen.getByText('Van Halen')
userEvent.click(faveBandItem)
// verify autocomplete has new value
expect(faveBand).toHaveValue('Van Halen')
Jag importerar userEvent, waitFor och screen på följande sätt...
import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
