25
loading...
This website collects cookies to deliver better user experience
getByText
or getByRole
, and you're off to the races.Found multiple elements with the text of: ${text}
function OfficeCharacters() {
const [characters, setCharacters] = useState([
'Michael Scott',
'Dwight Schrute',
'Jim Halpert'
]);
const [newCharacter, setNewCharacter] = useState('');
function add(e) {
e.preventDefault();
setCharacters((prev) => [newCharacter, ...prev]);
setNewCharacter('');
}
function deleteCharacter(character) {
setCharacters(
(prev) => prev.filter((c) => c !== character)
);
}
return (
<>
<form onSubmit={add}>
<label htmlFor="newCharacter">New Character</label>
<input
type="text"
id="newCharacter"
value={newCharacter}
onChange={(e) => setNewCharacter(e.target.value)}
/>
<button>Add</button>
</form>
<ul>
{characters.map((character, i) => (
<li key={i} data-testid="character">
<span data-testid="name">{character}</span>{' '}
<button
type="button"
onClick={() => deleteCharacter(character)}
>
Delete
</button>
</li>
))}
</ul>
</>
);
}
form
part of the component will be the easy part. Here's what we have:<form onSubmit={add}>
<label htmlFor="newCharacter">New Character</label>
<input
type="text"
id="newCharacter"
value={newCharacter}
onChange={(e) => setNewCharacter(e.target.value)}
/>
<button>Add</button>
</form>
describe("OfficeCharacters", () => {
function renderOfficeCharacters() {
render(<OfficeCharacters />);
return {
newCharacterInput:
screen.getByLabelText('New Character'),
addButton: screen.getByText('Add'),
};
}
});
<ul>
{characters.map((character, i) => (
<li key={i} data-testid="character">
<span data-testid="name">{character}</span>{' '}
<button
type="button"
onClick={() => deleteCharacter(character)}
>
Delete
</button>
</li>
))}
</ul>
getAllBy*
query to get all the names and then another query to get all the delete buttons. But then we'd have to stitch them back together based on index. Yeah... Let's not do that.within
.data-testid
to signal that it's only needed for testing.<li key={i} data-testid="character">
...
</li>
data-testid="character"
and get the name and delete button for each one.return {
newCharacterInput: screen.getByLabelText('New Character'),
addButton: screen.getByText('Add'),
getCharacters() {
return screen.getAllByTestId('character')
.map((item) => ({
name: within(item)
.getByTestId('name')
.textContent,
deleteButton: within(item)
.getByText('Delete')
}));
}
};
it('should add a character', () => {
const {
newCharacterInput,
addButton,
getCharacters
} = renderOfficeCharacters();
const pam = 'Pam Beesly';
// verify pam is NOT in the initial list
expect(
getCharacters().find(
(character) => character.name === pam
)
).not.toBeTruthy();
// add pam
fireEvent.change(
newCharacterInput,
{ target: { value: pam } }
);
fireEvent.click(addButton);
// verify pam is first in the list
expect(
getCharacters().findIndex(
(character) => character.name === pam
)
).toBe(0);
});
it('should delete a character', () => {
const { getCharacters } = renderOfficeCharacters();
const jim = 'Jim Halpert';
// get the delete button for Jim
const deleteJim = getCharacters().find(
(character) => character.name === jim
).deleteButton;
// delete Jim
fireEvent.click(deleteJim);
// verify Jim is NOT in list
expect(
getCharacters().find(
(character) => character.name === jim
)
).not.toBeTruthy();
});
data-testid
on the repeating container, and use within
to query the individual elements.