React Testing Library: Within

Opublikowano: 14.10.2023 - tagi: JavaScript React Testowanie Test Lista

Do czego służy within?

React Testing Library udostępnia funkcję o nazwie within, która pozwala na pisanie zapytań tylko w obrębie danego fragmentu z DOM.

Załóżmy, że mamy listę i każdy element listy składa się z innych elementów. Na przykład:

<ul>
	<li>
		<h2>Item 1</h2>
		<button type="button">Edit</button>
		<button type="button">Remove</button>
	</li>
	<li>
		<h2>Item 2</h2>
		<button type="button">Edit</button>
		<button type="button">Remove</button>
	</li>
</ul>

Teraz chcemy napisać test, który sprawdzi usuwanie elementu z listy. Jak pobrać referencję do przycisku: "Remove"? Można każdemu przycisku nadać unikalne id za pomocą data-testid.

Można też użyć funkcji within wystarczy, że przekażemy do niej pojedynczy element listy. Następnie za pomocą zapytania zdobyć referencję do przycisku.

Przykład

Poniżej znajduje się prosty kod TODO listy.

Komponent:

import {useState} from "react";

export function MyComponent() {
    const [tasks, setTasks] = useState(['Task 1', 'Task 2', 'Task 3']);
    const [task, setTask] = useState('');

    const onChangeTask = (e) => {
        setTask(e.target.value)
    };

    const onAddTask = (e) => {
        e.preventDefault();

        setTasks([task, ...tasks]);
        setTask('');
    }

    const onRemoveTask = (taskToRemove) => {
        setTasks(tasks.filter(currentTask => currentTask != taskToRemove))
    }

    return (
        <>
            <form onSubmit={onAddTask}>
                <label htmlFor="newTask">Task title:</label>
                <input type="text" id="newTask" value={task} onChange={onChangeTask} />
                <button>Add</button>
            </form>
            <ul>
                {
                    tasks.map((task, index) => (
                        <li key={index}>
                            <span data-testid="name">{task}</span>
                            <button type="button" onClick={() => onRemoveTask(task)}>Remove</button>
                        </li>
                    ))
                }
            </ul>
        </>
    )
}

I chcesz stestować usuwanie zadania (element listy).

Test:

import {render, screen, within} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {MyComponent} from "./MyComponent";

test('should remove task from list', async () => {
    // given
    const { getAllByRole, queryByText } = screen;
    render(<MyComponent />);
    const listItems = getAllByRole('listitem').map((item) => ({
        name: within(item).getByTestId('name').textContent,
        removeBtn: within(item).getByRole('button', { name: 'Remove' }),
    }));
    const removeTaskBtn = listItems.find((item) => item.name === 'Task 1').removeBtn;

    // when
    await userEvent.click(removeTaskBtn);

    // then
    expect(queryByText('Task 1')).not.toBeInTheDocument();
});

Najpierw iterujemy po wszystkich elementach listy za pomocą getAllByRole. Tworzona jest tablica, która zawiera nazwę elementu i referencję do przycisku do usuwania zadania.

Przy każdej iteracji korzystamy z funkcji within. Przekazywany jest do niej pojedynczy element listy. Dzięki temu możesz napisać zapytanie: Pobierz mi przycisk o nazwie "Remove". Nie ważne, że na liście znajduje się więcej niż jeden taki przycisk o tej samej nazwie!