React Testing Library: Jak pobrać referencję do elementu w komponencie?

Opublikowano: 05.08.2023 - tagi: JavaScript React Testy Komponent

Pobieranie referencji

Biblioteka React Testing Library daje programiście do ręki zestaw funkcji, które pozwalają na pobranie referencji do elementu w komponencie.

Te zapytania dzięlą się na trzy grupy:

  1. getBy - Zapytanie zwraca referencję do elementu. Dodatkowo zostanie zasygnalizowany błąd w dwóch przypadkach: jeśli referencja nie została znaleziona lub zostało znalezionych więcej niż jeden element.
  2. queryBy - Jeśli element nie został znaleziony zwróci null. To zapytanie jest przydatne kiedy potrzebujesz stestować przypadek, że dany element nie istnieje. Zasygnalizuje błąd jeśli zostało znalezionych więcej niż jeden element.
  3. findBy - Zwraca Promise, która zawiera referencję do danego elementu. Promise zostanie odrzucona jeśli element nie został odnaleziony lub jeśli znalezionych zostało więcej niż jeden element po upływie 1000 milisekund (1 sekunda). Przydaje się więc w operacjach asynchronicznych.

Jeśli potrzebujesz pobrać więcej niż jeden element po danych kryteriach możesz użyć też: getAllBy, queryAllBy, findAllBy.

Przykłady

getByRole

Służy do pobrania referencji do elementu bazując na roli, jaką pełni element w dokumencie, w rozumieniu szeroko pojętej dostępności (ang.: accessibility). Na przykład czy jest to: przycisk, nagłówek, lista itp.

Przykład:

Komponent:

export function MyComponent() {
    return (
        <>
            <div>
                <h1>My header</h1>
            </div>
        </>
    )
}

Test:

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

test('some test', () => {
    // given
    const { getByRole } = screen;
    render(<MyComponent />);
    const heading = getByRole('heading');

    // when - then
    expect(heading).toHaveTextContent('My header');
});

Można też pobrać element na podstawie jego nazwy:

Komponent:

import {useState} from "react";

export function MyComponent() {
    const [state, setState] = useState(0);

    return (
        <>
            <div>
                <h1>{state}</h1>
            </div>
            <div>
                <button onClick={() => setState((prevState: number) => prevState + 1)}>Increment</button>
                <button onClick={() => setState((prevState: number) => prevState - 1)}>Decrement</button>
            </div>
        </>
    )
}

Żeby zdobyć referencję dla przycisku o nazwie: Increment wystaczy napisać:

const btnIncrement = getByRole('button', { name: /Increment/i});

Zauważ, że użyte zostało tutaj wyrażenie regularne.

Można też po prostu napisać:

const btnIncrement = getByRole('button', { name: "Increment" });

getByLabelText

Dokumentacja zaleca używania tej funkcji, gdy potrzebujesz mieć dostęp do pola z formularza.

Zwykle każde pole ma własną etykietę (a przynajmniej powinno mieć). Więc naturalne jest, że będziemy szukać danego pola po jego etykiecie.

Przykład:

Komponent:

export function MyComponent() {
    return (
        <>
            <form>
                <label htmlFor="name">Name</label>
                <input id="name" value="Default name" />
            </form>
        </>
    )
}

Test:

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

test('should field has default value', () => {
    // given
    const { getByLabelText } = screen;
    render(<MyComponent />);
    const field = getByLabelText('Name');

    // when - then
    expect(field).toHaveValue('Default name');
});

getByDisplayValue

Jest podobne do getByLabelText różnica jest taka, że ta funkcja znajdzie Ci referencję, do elementu formularza na podstawie wartości, którą to pole zawiera.

Komponent:

export function MyComponent() {
    return (
        <>
            <form>
                <label htmlFor="name">Name</label>
                <input id="name" value="Default name" />
            </form>
        </>
    )
}

Test:

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

test('should field has default value', () => {
    // given
    const { getByDisplayValue } = screen;
    render(<MyComponent />);
    const field = getByDisplayValue('Default name');

    // when - then
    expect(field).toBeInTheDocument();
});

getByText

Tej funkcji należy używać, gdy chcemy pobrać referencję do elementów, które nie są interkatywne. Jak na przykład div'y, akapity itp.

Przykład:

Komponent:

import {useState} from "react";

export function MyComponent() {
    const [counter] = useState(10);

    return (
        <>
            <h1>Counter: {counter}</h1>
        </>
    )
}

Test:

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

test('should counter be 10 as default', () => {
    // given
    const { getByText } = screen;
    render(<MyComponent />);
    const counter = getByText('Counter: 10');

    // when - then
    expect(counter).toBeInTheDocument();
});

Podsumowanie: Lipiec 2023

Opublikowano: 31.07.2023 - tagi: Podsumowanie Lipiec Blog 2023

W lipcu opublikowałem 4 wpisy:


Blog

  1. Motyw graficzny

React

  1. Do czego służy memo?

MongoDB

  1. Jak skasować kolekcję?

Opublikowałem jeden komiks:

  1. The Comfort Zone

Przeczytałem dwie książki:

  1. Pogrzebany olbrzym - Kazuo Ishiguro
  2. Elegia dla bidoków - J. D. Vance

Przesłuchałem pięć audiobook'ów:

  1. 1000+ Little Things Happy Successful People Do Differently - Marc Chernoff, Angel Chernoff
  2. Mind Management, Not Time Management - David Kadavy
  3. The Pathless Path - Paul Millerd
  4. Drobiazgi takie jak te - Claire Keegan
  5. Martwe lwy - Mick Herron

MongoDB: Jak skasować kolekcję?

Opublikowano: 13.07.2023 - tagi: MongoDB Baza danych Komenda Kolekcja

Usuwanie kolekcji

Żeby usunąć kolekcję z MongoDB, należy:

Uruchom terminal

Wejdź do mongo poprzez polecenie:

mongo

Następnie musisz wybrać kolekcję, którą chcesz usunąć. Jeśli nie pamiętasz nazwy, wpisz:

show dbs;

Wyświetli się lista wszystkich kolekcji.

Mając już nazwę kolekcji, wywołaj komendę:

use collection-name;

Teraz możesz ją usunąć za pomocą:

db.dropDatabase();

React: Do czego służy memo?

Opublikowano: 11.07.2023 - tagi: React JavaScript Komponent Wydajność

Czym jest memo?

Kiedy stan komponentu się zmienia, React przerysowuje go na nowo. Dzieje się to także z innymi komponentami, które komponent-rodzic zawiera.

Może się zdarzyć, że takie przerysowywanie komponentu za każdym razem nie jest dobrym pomysłem. Wydajność aplikacji może spaść. I w takich przypadkach można skorzystać z memo. Po jego użyciu React zapamiętuje komponent i pomija (jeśli nie jest to potrzebne) ponowne przerysowanie komponentu.

Z memo należy korzystać rozważnie — tylko wtedy, gdy ma to sens.

Jak działa memo?

Przykład użycia:

import React, { memo } from 'react';

function User({ name, age }) {
    return (
        <>
            <div>Name: <strong>{name}</strong></div>
            <div>Age: <strong>{age}</strong></div>
        </>
    );
}

const MemoizedUser = memo(User);

<MemoizedUser name="Janusz" age="18"></MemoizedUser>

Żeby komponent został "zapamiętany" przekazujemy go do funkcji memo. Od tego momentu, gdy użyjemy tak przygotowany komponent (MemoizedUser), React będzie używać jego zapamiętaną wersję, dopóty dopóki jego właściwości: name lub age będą mieć te same wartości. Jeśli wartość dla przynajmniej jednego parametru komponentu zostanie zmieniona, to zostanie on przerysowany i zapamiętany dla nowych wartości.

Składnia memo

Funkcja memo zawiera więcej niż jeden parametr:

memo(Component, arePropsEqual?)

Component — Tutaj przekazujemy komponent, który chcemy, żeby został zapamiętany.

arePropsEqual — Jest to parametr opcjonalny. To funkcja, która informuje React, czy komponent powinien zostać przerysowany. Przyjmuje dwa parametry: pierwszy to wartości z poprzedniego stanu komponentu. Drugi aktualne wartości stanu. Ta funkcja powinna zwrócić true jeśli oba stany mają te samo wartości — nic się nie zmieniło, więc komponent nie musi być przerysowany.

Przykład takiej funkcji:

function areUserPropsEqual(prev, next) {
	return prev.name === next.name &&
		   prev.age === next.age;
}

const MemoizedUser = memo(User, areUserPropsEqual);

Kiedy używać memo?

  1. Komponent jest "czysty" - Dla tych samych wartości zawsze wyświetla ten sam wynik.
  2. Komponent jest często przerysowywany.
  3. Komponent staje się coraz większy. Przez co jego częste przerysowywanie staje się kosztowne.
  4. Komponent jest często przerysowywany z tymi samymi wartościami.

Kiedy nie używać memo?

  1. Komponent zwykle jest wyświetlany z różnymi wartościami.
  2. Komponent nie jest duży i nie robi czasochłonnych obliczeń.

Uwaga na funkcje zwrotne!

Kiedy przekazujesz do komponentu funkcję zwrotną (ang. callback) i zamierzasz skorzystać z memo, uważaj, jak to robisz. Możesz popsuć zapamiętywanie komponentu!

Przykład:

import React, { useState, memo } from 'react';

function User({ name, age, onSendMessage }) {
    return (
        <>
            <div>Name: <strong>{name}</strong></div>
            <div>Age: <strong>{age}</strong></div>
            <button onClick={onSendMessage}>Send message</button>
        </>
    );
}

const MemoizedUser = memo(User);

const App = () => {
    const [user, setUser] = useState({
        name: 'Janusz',
        age: 18
    });

    return (
        <>
            <div>
                <button onClick={() => setUser({
                    name: 'Janusz',
                    age: 15.99
                })}>Janusz</button>
                <button onClick={() => setUser({
                    name: 'Grażyna',
                    age: 55
                })}>Grażyna</button>
            </div>
            <MemoizedUser 
                name={user.name}
                age={user.age}
                onSendMessage={
                    () => console.log(`Send message to: ${user.name}`)
            }></MemoizedUser>
        </>
    );
}

W powyższym przykładzie zapamiętywanie komponentu przez memo jest popsute. Dodaj console.log w komponencie User i klikaj w przycisk "Janusz". Komponent MemoizedUser cały czas jest przerysowywany na nowo? Co się dzieje?!

Problem jest w tej linijce:

onSendMessage={() => console.log(`Send message to: ${user.name}`)

Za każdym razem, gdy stan komponentu App się zmienia do onSendMessage przekazywana jest nowa instancja funkcji zwrotnej. A skoro nowa wartość to komponent MemoizedUser zostanie przerysowany na nowo! I tak za każdym razem...

Można to rozwiązać za pomocą useCallback:

import React, { useState, useCallback, memo } from 'react';

const App = () => {
    const [user, setUser] = useState({
        name: 'Janusz',
        age: 18
    });

    const sendMessage = useCallback(() => {
        console.log(`Send message to: ${user.name}`)
    }, [user.name]);

    return (
        <>
            <div>
                <button onClick={() => setUser({
                    name: 'Janusz',
                    age: 15.99
                })}>Janusz</button>
                <button onClick={() => setUser({
                    name: 'Grażyna',
                    age: 55
                })}>Grażyna</button>
            </div>
            <MemoizedUser
                name={user.name}
                age={user.age}
                onSendMessage={sendMessage}></MemoizedUser>
        </>
    );
}

Comics: The Comfort Zone

Opublikowano: 08.07.2023 - tagi: Komiks Rysowanie Rozwój

The comfort zone