Принципы SOLID в React: так ли все с ними гладко? (Часть 2)
Статья (рус.):https://nuancesprog.ru/p/19540/
Принцип разделения интерфейсов
Компонент не должен зависеть от свойств, которые он не использует. Прекрасный принцип, с которым сложно поспорить.
В качестве примера приводится компонент UserAvatar, который в качестве пропа получает объект типа User. И хотя аватар исползует только пару свойств юзера, он все равно вынужден знать об этом типе. Правильнее будет передать в компонент только те пропсы, которые ему действительно нужны - изменить его интерфейс.
Принцип инверсии зависимостей
Реализация должна зависеть от абстракций и не должна зависеть от других реализаций.
React в принципе построен на этом принципе, мы используем его, даже не замечая.
У нас есть два вида зависимостей:
- прямая зависимость - это импорты. Мы прямо импортируем что-то из соответствующих модулей, и если в этих модулях что-то изменится, нам будет больно.
- обратная зависимость - это то, что мы получаем в виде параметров. От них мы тоже зависим, но хотя бы можем их контролировать - определить абстракцию - интерфейс.
В итоге наш компонент UserAvatar в исходном виде нарушает сразу три приципа SOLID:
- единой ответственности (компонент в курсе бизнес-логики, знает, как устроен user)
- разделения интерфейсов
- инверсии зависимостей (импортирует вместо того, чтобы принимать, зависит от реализации, а не от абстракции)
Автор отдельно отмечает, что принцип инверсии зависимостей это не только про обработчики событий, которые мы передаем дочерним компонентам. На самом деле композиция в React - это и есть инверсия зависимостей.
Проп children - это тоже инверсия зависимостей. Благодаря ей компонент-работник, который ничего не должен знать о бизнес-логике, может содержать в себе компоненты-менеджеры, не нарушая ни одного принципа, так как ничего об этом не знает. Более того, компонент даже может определить интерфейс для этого пропа (например, принимать только строку) - то есть зависит от абстракции.
В React инверсия зависимостей - это перенос ответственности на родителя. Важно соблюдать разумные границы в реализации этого принципа, ведь ответственности может стать очень много. Иногда можно использовать и прямые зависимости.
#ссылки #паттерны
Статья (рус.):https://nuancesprog.ru/p/19540/
Принцип разделения интерфейсов
Компонент не должен зависеть от свойств, которые он не использует. Прекрасный принцип, с которым сложно поспорить.
В качестве примера приводится компонент UserAvatar, который в качестве пропа получает объект типа User. И хотя аватар исползует только пару свойств юзера, он все равно вынужден знать об этом типе. Правильнее будет передать в компонент только те пропсы, которые ему действительно нужны - изменить его интерфейс.
Принцип инверсии зависимостей
Реализация должна зависеть от абстракций и не должна зависеть от других реализаций.
React в принципе построен на этом принципе, мы используем его, даже не замечая.
У нас есть два вида зависимостей:
- прямая зависимость - это импорты. Мы прямо импортируем что-то из соответствующих модулей, и если в этих модулях что-то изменится, нам будет больно.
- обратная зависимость - это то, что мы получаем в виде параметров. От них мы тоже зависим, но хотя бы можем их контролировать - определить абстракцию - интерфейс.
В итоге наш компонент UserAvatar в исходном виде нарушает сразу три приципа SOLID:
- единой ответственности (компонент в курсе бизнес-логики, знает, как устроен user)
- разделения интерфейсов
- инверсии зависимостей (импортирует вместо того, чтобы принимать, зависит от реализации, а не от абстракции)
Автор отдельно отмечает, что принцип инверсии зависимостей это не только про обработчики событий, которые мы передаем дочерним компонентам. На самом деле композиция в React - это и есть инверсия зависимостей.
Проп children - это тоже инверсия зависимостей. Благодаря ей компонент-работник, который ничего не должен знать о бизнес-логике, может содержать в себе компоненты-менеджеры, не нарушая ни одного принципа, так как ничего об этом не знает. Более того, компонент даже может определить интерфейс для этого пропа (например, принимать только строку) - то есть зависит от абстракции.
В React инверсия зависимостей - это перенос ответственности на родителя. Важно соблюдать разумные границы в реализации этого принципа, ведь ответственности может стать очень много. Иногда можно использовать и прямые зависимости.
#ссылки #паттерны
NOP::Nuances of programming
Принципы SOLID в React: так ли все с ними гладко?
Принципы SOLID принято описывать только в положительном свете. Поэтому многие нюансы часто упускаются из виду. Сегодня поговорим о том, применимы ли вообще принципы SOLID в React-приложении и какими ключевыми особенностями обладает каждый из них.
👍6
Зачем писать юнит-тесты на фронтенд?
Статья (рус.): https://habr.com/ru/companies/nordclan/articles/755302/
Статья для тех, кто (я 🙃) очень плохо понимает, как вообще тестировать фронтенд. Методички не будет, зато будет практический пример, очень понятный.
Самый важный посыл - нужно отделять логику от UI - и помещать ее, например, в хуки. Тогда мы сможем отдельно протестировать хук. Кроме того, мы сможем подкинуть компоненту поддельную логику и проверить, как он с ней взаимодействует.
В качестве примера у нас тут игра судоку. Автор сначала пишет код без тестов и засовывает всю логику прямо в компонент, пишет тесты на то, что в ячейку можно ввести значение, но быстро сталкивается с тем, что никак нельзя проверить состояние игры - сохранилось ли введенное значение.
Пункт 1 - сначала пишем тесты, потом реализацию.
Тогда автор выносит всю логику работы с игровым полем в хук и тестирует отдельно хук. Когда речь идет о логике, кажется, вполне понятно, что нужно тестировать. Проверяем начальное состояние поля. Пытаемся изменить значение в конкретной ячейке и проверяем измененное состояние.
Пункт 2 - отделяем логику от UI.
А дальше финт ушами - мы передаем наш хук в компонент в качестве пропа! То есть всю логику целиком передаем в компонент, чтобы он ее использовал, не задумываясь о том, что там под капотом.
(Тут кстати не забываем про принцип инверсии зависимостей. Компонент строго определяет интерфейс пропа, который он хочет видеть, и полностью полагается на него, а не на конкретную реализацию.)
Теперь при тестировании компонента мы, например, можем подкинуть ему фальшивый хук, в котором замоканы все нужные методы.
Что же тестировать в самом компоненте?
Например то, что он вызывает нужный метод в нужный момент. Мы имитируем ввод данных в ячейку и проверяем, был ли вызван метод, изменяющий состояние.
Пункт 3 - когда логика протестирована, мы тестируем взаимодействие UI с логикой.
#ссылки #тестирование
Статья (рус.): https://habr.com/ru/companies/nordclan/articles/755302/
Статья для тех, кто (я 🙃) очень плохо понимает, как вообще тестировать фронтенд. Методички не будет, зато будет практический пример, очень понятный.
Самый важный посыл - нужно отделять логику от UI - и помещать ее, например, в хуки. Тогда мы сможем отдельно протестировать хук. Кроме того, мы сможем подкинуть компоненту поддельную логику и проверить, как он с ней взаимодействует.
В качестве примера у нас тут игра судоку. Автор сначала пишет код без тестов и засовывает всю логику прямо в компонент, пишет тесты на то, что в ячейку можно ввести значение, но быстро сталкивается с тем, что никак нельзя проверить состояние игры - сохранилось ли введенное значение.
Пункт 1 - сначала пишем тесты, потом реализацию.
Тогда автор выносит всю логику работы с игровым полем в хук и тестирует отдельно хук. Когда речь идет о логике, кажется, вполне понятно, что нужно тестировать. Проверяем начальное состояние поля. Пытаемся изменить значение в конкретной ячейке и проверяем измененное состояние.
Пункт 2 - отделяем логику от UI.
А дальше финт ушами - мы передаем наш хук в компонент в качестве пропа! То есть всю логику целиком передаем в компонент, чтобы он ее использовал, не задумываясь о том, что там под капотом.
(Тут кстати не забываем про принцип инверсии зависимостей. Компонент строго определяет интерфейс пропа, который он хочет видеть, и полностью полагается на него, а не на конкретную реализацию.)
Теперь при тестировании компонента мы, например, можем подкинуть ему фальшивый хук, в котором замоканы все нужные методы.
Что же тестировать в самом компоненте?
Например то, что он вызывает нужный метод в нужный момент. Мы имитируем ввод данных в ячейку и проверяем, был ли вызван метод, изменяющий состояние.
Пункт 3 - когда логика протестирована, мы тестируем взаимодействие UI с логикой.
#ссылки #тестирование
Хабр
Зачем писать юнит-тесты на фронтенд?
Привет, хабр! Меня зовут Александр, я работаю фронтенд-разработчиком в компании Nord Clan. Сегодня речь пойдет про тесты… Про юнит-тесты. Думаю, что почти все слышали про юнит-тесты, пробовали их...
👍2🔥1
Testing Library. Вступление
Незапланированно и стремительно перехожу к рассмотрению Testing Library и ее субпакетов.
Вот тут сайтик с документацией и примерами: https://testing-library.com/
Что это вообще за штука такая?
По сути это набор утилит для тестирования DOM, то есть нашего с вами фронтенда. Они работают с любым DOM-подобным ресурсом и помогают находить элементы и эмулировать события, как это мог бы делать пользователь.
Философия Testing Library:
«Чем больше тесты похожи на реальное использование вашего продукта, тем больше уверенности они дают».
Эта фраза преследует вас по всей доке. Она означает, что мы максимально отходим от реализации и работаем только с тем, что есть на странице - что видит пользователь. Тесты ничего не знают о вашем коде, а значит, они не сломаются, если код изменится.
Testing Library может работать с реальным DOM из браузера, а также с его эмуляциями (JSDOM, Jest).
У них есть пакеты с базовой функциональностью, а также отдельные модули для популярных фреймворков, которые добавляют специфические утилиты.
Итак, Testing Library это большой набор хелперов для работы с DOM в стиле реального пользователя. Ее удобно использовать в тестах, но это не тест-раннер и не полноценный фреймворк для тестирования - просто хелпер, не привязанный к другим технологиям (только к DOM).
Утверждается, что мы можем использовать Testing Library для любых видов тестов, от юнитов до e2e (есть даже интеграция с cypress).
Итак, основной принцип: мы работаем с DOM, а не с инстансами компонентов.
#тестирование #testinglibrary #документация
Незапланированно и стремительно перехожу к рассмотрению Testing Library и ее субпакетов.
Вот тут сайтик с документацией и примерами: https://testing-library.com/
Что это вообще за штука такая?
По сути это набор утилит для тестирования DOM, то есть нашего с вами фронтенда. Они работают с любым DOM-подобным ресурсом и помогают находить элементы и эмулировать события, как это мог бы делать пользователь.
Философия Testing Library:
«Чем больше тесты похожи на реальное использование вашего продукта, тем больше уверенности они дают».
Эта фраза преследует вас по всей доке. Она означает, что мы максимально отходим от реализации и работаем только с тем, что есть на странице - что видит пользователь. Тесты ничего не знают о вашем коде, а значит, они не сломаются, если код изменится.
Testing Library может работать с реальным DOM из браузера, а также с его эмуляциями (JSDOM, Jest).
У них есть пакеты с базовой функциональностью, а также отдельные модули для популярных фреймворков, которые добавляют специфические утилиты.
Итак, Testing Library это большой набор хелперов для работы с DOM в стиле реального пользователя. Ее удобно использовать в тестах, но это не тест-раннер и не полноценный фреймворк для тестирования - просто хелпер, не привязанный к другим технологиям (только к DOM).
Утверждается, что мы можем использовать Testing Library для любых видов тестов, от юнитов до e2e (есть даже интеграция с cypress).
Итак, основной принцип: мы работаем с DOM, а не с инстансами компонентов.
#тестирование #testinglibrary #документация
Testing-Library
Testing Library | Testing Library
Simple and complete testing utilities that encourage good testing practices
👍3
Testing Library. Запросы
Основная задача при работе с DOM - поиск нужных элементов. За это у нас отвечает пакет @testing-library/dom.
Что искать?
Testing Library придерживается философии, что на элементы нужно смотреть с точки зрения пользователя, а не программиста.
⁃ Пользователь ищет элемент не по классу или testid, а прежде всего по его роли: например, поле ввода или кнопка.
⁃ Элементы формы можно искать по тексту связанного с ними элементу label или по плейсхолдеру. В крайнем случае по значению, которое находится в поле в данный момент.
⁃ Неинтерактивные элементы (параграфы, дивы) можно искать по тексту.
⁃ Есть еще варианты искать по тексту атрибутов alt и title, но он уже менее желательный.
⁃ И наконец в конце списка идет поиск по testid.
Как искать?
Библиотека предоставляет целую кучу методов для поиска, разделенных на три группы - отличаются мелкими деталями.
⁃ getBy… и getAllBy… (например, `getByRole`)
⁃ queryBy… и queryAllBy…
⁃ findBy… и findAllBy…
Очевидно, что методы с префиксами
Первое отличие состоит в том, как этим методы реагируют на неудачу - если не нашлось ничего. Методы группы
Вторая особенность - методы
Пробуем
Все эти методы можно импортировать из библиотеки напрямую. В качестве первого аргумента они ожидают HTML-контейнер, внутри которого нужно искать.
Рабочий пример: https://codesandbox.io/p/devbox/compassionate-mestorf-68kddy
#тестирование #testinglibrary #документация #примерыкода
Основная задача при работе с DOM - поиск нужных элементов. За это у нас отвечает пакет @testing-library/dom.
Что искать?
Testing Library придерживается философии, что на элементы нужно смотреть с точки зрения пользователя, а не программиста.
⁃ Пользователь ищет элемент не по классу или testid, а прежде всего по его роли: например, поле ввода или кнопка.
⁃ Элементы формы можно искать по тексту связанного с ними элементу label или по плейсхолдеру. В крайнем случае по значению, которое находится в поле в данный момент.
⁃ Неинтерактивные элементы (параграфы, дивы) можно искать по тексту.
⁃ Есть еще варианты искать по тексту атрибутов alt и title, но он уже менее желательный.
⁃ И наконец в конце списка идет поиск по testid.
Как искать?
Библиотека предоставляет целую кучу методов для поиска, разделенных на три группы - отличаются мелкими деталями.
⁃ getBy… и getAllBy… (например, `getByRole`)
⁃ queryBy… и queryAllBy…
⁃ findBy… и findAllBy…
Очевидно, что методы с префиксами
getBy
, queryBy
и findBy
ищут и возвращают один элемент. При этом если найдется несколько, то все они выбросят ошибку. Методы getAllBy
, queryAllBy
и findAllBy
ищут все подходящие элементы и возвращают массив.Первое отличие состоит в том, как этим методы реагируют на неудачу - если не нашлось ничего. Методы группы
query
не очень драматизируют по этому поводу и возвращают либо null
, либо пустой массив. Остальные сразу кидают ошибку.Вторая особенность - методы
find
возвращают промисы. Благодаря этому они могут «ждать», пока нужный элемент появится на странице. Остальные по умолчанию так не могут, если элемента нет, так нет.Пробуем
Все эти методы можно импортировать из библиотеки напрямую. В качестве первого аргумента они ожидают HTML-контейнер, внутри которого нужно искать.
Рабочий пример: https://codesandbox.io/p/devbox/compassionate-mestorf-68kddy
#тестирование #testinglibrary #документация #примерыкода
👍4
Testing Library. Screen
Итак, в Testing Library есть куча методов для поиска элементов в DOM, но они все требуют передавать им первым аргументом HTML-контейнер.
Есть другой подход, более удобный. Библиотека экспортирует объект screen, в котором тоже есть все эти методы, но уже с предустановленным контейнером. Как нетрудно догадаться, в его роли выступает document.body.
Однако как будто нет способа установить свой собственный body для screen, он берется из глобального скоупа. То есть у нас должен быть глобальный document, но так как мы не работаем с браузером, его нет.
Можно попробовать установить его искусственно:
Но у меня что-то не получилось, все равно кидает ошибку, что document.body недоступно.
Поэтому мы возьмем вместо jsdom другой пакет, который сделает это за нас (возьмет jsdom и закинет его на глобальный уровень, как будто мы в браузере). Дальше можно будет работать как будто внутри браузера.
Пакет называется global-jsdom, а чтобы все завелось нужно подключить модуль ‘global-jsdom/register’.
Этих эманаций оказалось достаточно, чтобы screen соизволил заработать, так что теперь можем брать методы у него.
Работающий пример тут: https://codesandbox.io/p/devbox/testing-library-global-jsdom-screen-react-junior-forked-8hrw8y
#тестирование #testinglibrary #документация #примерыкода
Итак, в Testing Library есть куча методов для поиска элементов в DOM, но они все требуют передавать им первым аргументом HTML-контейнер.
Есть другой подход, более удобный. Библиотека экспортирует объект screen, в котором тоже есть все эти методы, но уже с предустановленным контейнером. Как нетрудно догадаться, в его роли выступает document.body.
Однако как будто нет способа установить свой собственный body для screen, он берется из глобального скоупа. То есть у нас должен быть глобальный document, но так как мы не работаем с браузером, его нет.
Можно попробовать установить его искусственно:
const dom = new JSDOM(‘’)
global.window = dom.window;
global.document = dom.window.document;
Но у меня что-то не получилось, все равно кидает ошибку, что document.body недоступно.
Поэтому мы возьмем вместо jsdom другой пакет, который сделает это за нас (возьмет jsdom и закинет его на глобальный уровень, как будто мы в браузере). Дальше можно будет работать как будто внутри браузера.
Пакет называется global-jsdom, а чтобы все завелось нужно подключить модуль ‘global-jsdom/register’.
require('global-jsdom/register')
// теперь можно взаимодействовать с DOM глобально
document.body.innerHTML = ''
Этих эманаций оказалось достаточно, чтобы screen соизволил заработать, так что теперь можем брать методы у него.
Работающий пример тут: https://codesandbox.io/p/devbox/testing-library-global-jsdom-screen-react-junior-forked-8hrw8y
#тестирование #testinglibrary #документация #примерыкода
👍3
Testing Library. Расширение и песочница
Есть прикольное расширение, которое помогает выбрать подходящий метод для поиска элемента на странице: https://chromewebstore.google.com/detail/testing-playground/hejbmebodbijjdhflfknehhcgaklhano
Устанавливаем его, в DevTools появляется новая вкладка Testing Playground.
Открываем ее, наводим курсор на нужный элемент, и расширение пытается подобрать самый подходящий метод с учетом всех best practices.
А еще есть песочница, где можно ввести любой кусок HTML и поиграться с запросами - https://testing-playground.com/
#тестирование #testinglibrary #документация
Есть прикольное расширение, которое помогает выбрать подходящий метод для поиска элемента на странице: https://chromewebstore.google.com/detail/testing-playground/hejbmebodbijjdhflfknehhcgaklhano
Устанавливаем его, в DevTools появляется новая вкладка Testing Playground.
Открываем ее, наводим курсор на нужный элемент, и расширение пытается подобрать самый подходящий метод с учетом всех best practices.
А еще есть песочница, где можно ввести любой кусок HTML и поиграться с запросами - https://testing-playground.com/
#тестирование #testinglibrary #документация
Google
Testing Playground - Chrome Web Store
Simple and complete DOM testing playground that encourage good testing practices.
👍3
Forwarded from Cat in Web
React Reconciliation: как работает и зачем нам это знать
Статья (англ.): https://www.developerway.com/posts/reconciliation-in-react
Перевод: https://gist.github.com/zagazat/db926ec7ab69061934246a55b64913c3
Реконсиляция в React - это процесс сравнения старого и нового состояния приложения, определение, что изменилось и что нужно перерендерить.
React берет старый и обновленный виртуальный DOM и сравнивает их.
Идея в том, чтобы найти оптимальный способ перерендеринга элементов в реальном DOM, чтобы улучшить производительность. React ищет все возможности, чтобы не создавать новые элементы, а переиспользовать уже существующие, просто внеся в них минимальные изменения.
В статье Надя Макаревич рассматривает некоторые неочевидные подводные камни такой оптимизации и способы их решения. Например, в некоторых ситуациях React переиспользует старый элемент (включая его стейт), даже если мы этого не хотим.
В качестве решения проблемы рассматриваются два подхода: с позицией элемента в массиве и с атрибутом key.
#react
Статья (англ.): https://www.developerway.com/posts/reconciliation-in-react
Перевод: https://gist.github.com/zagazat/db926ec7ab69061934246a55b64913c3
Реконсиляция в React - это процесс сравнения старого и нового состояния приложения, определение, что изменилось и что нужно перерендерить.
React берет старый и обновленный виртуальный DOM и сравнивает их.
Идея в том, чтобы найти оптимальный способ перерендеринга элементов в реальном DOM, чтобы улучшить производительность. React ищет все возможности, чтобы не создавать новые элементы, а переиспользовать уже существующие, просто внеся в них минимальные изменения.
В статье Надя Макаревич рассматривает некоторые неочевидные подводные камни такой оптимизации и способы их решения. Например, в некоторых ситуациях React переиспользует старый элемент (включая его стейт), даже если мы этого не хотим.
В качестве решения проблемы рассматриваются два подхода: с позицией элемента в массиве и с атрибутом key.
#react
Developerway
React reconciliation: how it works and why should we care
Looking in detail into how React reconciliation algorithm works. Explore in the process quirks of conditional rendering, the 'key' attribute, and why we shouldn't declare components inside other components.
👍3
byRole
Итак, начнем с подробного разбора самого главного запроса в Testing Library:
Документация: https://testing-library.com/docs/queries/byrole
Собственно роль
Первым параметром метод принимает собственно роль нужного элемента в виде строки. Это может быть
Речь идет не об атрибуте
Опции
Вторым параметром можно передать объект с кучей дополнительных опций, которые помогут найти нужный элемент. Например, если у вас на странице куча кнопок, а нужно найти конкретную.
Тут будет много опций для поиска по
🔸hidden: boolean
По умолчанию значение равно false - из-за этого в поиск не включаются "недоступные" элементы (display: none, aria-hidden, role=none).
🔸name: TextMatch
Для элемента формы - это текст лейбла, для кнопки - собственно текст кнопки. Также может использоваться значение атрибута
Что за тип такой
Функция принимает два аргумента: content: string (собственно текстовый контент, по которому производится поиск) и element (DOMElement) и должна вернуть true или false.
🔸description: TextMatch
Это для атрибута aria-describedby.
🔸selected: boolean
Отбор по значению атрибута aria-selected.
🔸busy: boolean
Отбор по значению атрибута aria-busy.
🔸checked: boolean
Отбор по значению атрибута aria-checked.
🔸pressed: boolean
Отбор по значению атрибута aria-pressed.
🔸suggest: boolean
Эта настройка для того, чтобы Testing Library предлагала вам запросы получше, чем тот, что написали вы.
🔸current: boolean | string
Отбор по значению атрибута aria-current.
🔸expanded: boolean
Отбор по значению атрибута aria-expanded.
🔸queryFallbacks: boolean
По умолчанию учитывается только первая роль каждого элемента, но есть поставить тут true, то будут приняты во внимание и запасные роли, если они есть.
🔸level: number
Уровень заголовка для роли
🔸value
Это для группы атрибутов
#тестирование #testinglibrary #документация
Итак, начнем с подробного разбора самого главного запроса в Testing Library:
byRole
. Это методы getByRole
, getAllByRole
, queryByRole
, queryAllByRole
, findByRole
, findAllByRole
.Документация: https://testing-library.com/docs/queries/byrole
Собственно роль
Первым параметром метод принимает собственно роль нужного элемента в виде строки. Это может быть
button
, heading
, switch
и так далее. Речь идет не об атрибуте
role
, а прежде всего о дефолтной (встроенной) роли элементов. Например, элемент button
имеет роль button
, даже без явного указания на это. Вот тут можно почитать про роли элементов.Опции
Вторым параметром можно передать объект с кучей дополнительных опций, которые помогут найти нужный элемент. Например, если у вас на странице куча кнопок, а нужно найти конкретную.
Тут будет много опций для поиска по
aria-
атрибутам.🔸hidden: boolean
По умолчанию значение равно false - из-за этого в поиск не включаются "недоступные" элементы (display: none, aria-hidden, role=none).
🔸name: TextMatch
Для элемента формы - это текст лейбла, для кнопки - собственно текст кнопки. Также может использоваться значение атрибута
aria-label
.Что за тип такой
TextMatch
? Это составной тип, который может быть обычной строкой, регулярным выражением или даже функцией.Функция принимает два аргумента: content: string (собственно текстовый контент, по которому производится поиск) и element (DOMElement) и должна вернуть true или false.
screen.getByText((content, element) => content.startsWith('Hello'))
🔸description: TextMatch
Это для атрибута aria-describedby.
🔸selected: boolean
Отбор по значению атрибута aria-selected.
🔸busy: boolean
Отбор по значению атрибута aria-busy.
🔸checked: boolean
Отбор по значению атрибута aria-checked.
🔸pressed: boolean
Отбор по значению атрибута aria-pressed.
🔸suggest: boolean
Эта настройка для того, чтобы Testing Library предлагала вам запросы получше, чем тот, что написали вы.
🔸current: boolean | string
Отбор по значению атрибута aria-current.
🔸expanded: boolean
Отбор по значению атрибута aria-expanded.
🔸queryFallbacks: boolean
По умолчанию учитывается только первая роль каждого элемента, но есть поставить тут true, то будут приняты во внимание и запасные роли, если они есть.
🔸level: number
Уровень заголовка для роли
heading
. Учитывает как семантику тега, так и атрибут aria-level
.🔸value
Это для группы атрибутов
aria-value
, например, aria-valuemin
, aria-valuetext
. Указывается в виде объекта:
screen.getByRole('spinbutton', { value: { min: 5, max: 10 }})
#тестирование #testinglibrary #документация
Testing-Library
ByRole | Testing Library
getByRole, queryByRole, getAllByRole, queryAllByRole, findByRole,
👍2
ByLabelText, ByPlaceholderText
Два запроса, предназначенных в основном для интерактивных элементов форм: поиск по тексту лейбла и по плейсхолдеру.
ByLabelText
Поле ввода может быть связано с лейблом разными способами:
- через атрибуты for и id
- через атрибут aria-labelledby
- если поле находится внутри label
- лейбл можно указать в атрибуте aria-label
Сигнатура:
Текст лейбла можно задать первым параметром в виде строки, регулярки или функции (TextMatch).
Вторым параметром идет объект настроект:
- selector - можно дополнительно указать селектор нужного элемента
- exact - если текст задан в виде строки, то этот параметр определяет должен ли поиск быть точным (по полной строке, с учетом регистра символов) или нет
- normalizer - по умолчанию Testing Library нормализует текст (убирает лишние пробелы). Можно передать собственный нормализатор
ByPlaceholderText
Очень похожий метод, только ищет элемент по тексту плейсхолдера. Менее предпочтительный, следует использовать только если элемент нельзя найти по лейблу или роли.
Сигнатура:
#тестирование #testinglibrary #документация #примерыкода
Два запроса, предназначенных в основном для интерактивных элементов форм: поиск по тексту лейбла и по плейсхолдеру.
ByLabelText
Поле ввода может быть связано с лейблом разными способами:
- через атрибуты for и id
- через атрибут aria-labelledby
- если поле находится внутри label
- лейбл можно указать в атрибуте aria-label
Сигнатура:
screen.getByLabelText(
text: TextMatch,
options: {
selector?: string = '*',
exact?: boolean = true,
normalizer?: NormalizeFn
}
)
Текст лейбла можно задать первым параметром в виде строки, регулярки или функции (TextMatch).
Вторым параметром идет объект настроект:
- selector - можно дополнительно указать селектор нужного элемента
- exact - если текст задан в виде строки, то этот параметр определяет должен ли поиск быть точным (по полной строке, с учетом регистра символов) или нет
- normalizer - по умолчанию Testing Library нормализует текст (убирает лишние пробелы). Можно передать собственный нормализатор
ByPlaceholderText
Очень похожий метод, только ищет элемент по тексту плейсхолдера. Менее предпочтительный, следует использовать только если элемент нельзя найти по лейблу или роли.
Сигнатура:
screen.getByPlaceholderText(
text: TextMatch,
options: {
exact?: boolean = true,
normalizer?: NormalizerFn
}
)
#тестирование #testinglibrary #документация #примерыкода
👍2
ByDisplayValue
Еще один запрос для интерактивных элементов, у которых может быть value (input, textarea, select). Соответственно поиск происходит по текущему значению value - это текст, который отображается в инпуте. Настроек у запроса немного:
#тестирование #testinglibrary #документация #примерыкода
Еще один запрос для интерактивных элементов, у которых может быть value (input, textarea, select). Соответственно поиск происходит по текущему значению value - это текст, который отображается в инпуте. Настроек у запроса немного:
screen.getByDisplayValue(
value: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}
)
#тестирование #testinglibrary #документация #примерыкода
👍1
ByText
Есть возможность искать элементы по текстовому контенту. Это сработает для всех элементов, у которых есть
Настройки у запроса стандартные:
Новая для нас настройка -
#тестирование #testinglibrary #документация #примерыкода
Есть возможность искать элементы по текстовому контенту. Это сработает для всех элементов, у которых есть
textContent
, а также для инпутов с типом submit
или button
.Настройки у запроса стандартные:
getByText(
text: TextMatch,
options?: {
selector?: string = '*',
exact?: boolean = true,
ignore?: string|boolean = 'script, style',
normalizer?: NormalizerFn,
})
Новая для нас настройка -
ignore
, она указывает, какие селекторы игнорировать при поиске.#тестирование #testinglibrary #документация #примерыкода
👍1
ByAltText, ByTitle
Еще два запроса для поиска по значению атрибутов:
-
-
#тестирование #testinglibrary #документация #примерыкода
Еще два запроса для поиска по значению атрибутов:
-
alt
- в основном для изображений-
title
screen.getByAltText(
text: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
})
screen.getByTitle(
title: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
})
#тестирование #testinglibrary #документация #примерыкода
👍1
ByTestId
И наконец последний, запасной запрос, который рекомендуется использовать только в самом крайнем случае, если ничего другое вам не подошло - поиск по атрибуту data-testid (название атрибута можно изменить в настройках).
#тестирование #testinglibrary #документация #примерыкода
И наконец последний, запасной запрос, который рекомендуется использовать только в самом крайнем случае, если ничего другое вам не подошло - поиск по атрибуту data-testid (название атрибута можно изменить в настройках).
screen.getByTestId(
text: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
})
#тестирование #testinglibrary #документация #примерыкода
👍1
Родители (parents) и владельцы (owners) в React
Статьи (англ.):
Поток данных https://julesblom.com/writing/parents-owners-data-flow
Производительность рендеринга https://julesblom.com/writing/parents-owners-performance
Статьи очень подробно и с интерактивными примерами рассказывают о двух способах построения дерева рендеринга в React (Parent Hierarchy и Owner Hierarchy). И о том, как понимание разницы между ними позволяет нам избежать prop drilling, а также улучшить производительность рендеринга без использования
Пример
Компонент App рендерит Dashboard, а тот в свою очередь рендерит WelcomeMessage. В компоненте App определяется стейт
Компоненты со слотами
Ту же функциональность можно реализовать по-другому:
Тут Dashboard реализован как компонент «со слотом». Он рендерит кусок приложения, не имея представления о том, что в нем происходит. Это инверсия контроля, когда ответственность за этот фрагмент переходит к родителю Dashboard.
Благодаря такой структуре мы избегаем пробрасывания пропсов через незаинтересованные компоненты.
На этом примере легко понять разницу между родителем (непосредственная обертка) и владельцем (компонент, который контролирует рендеринг). Для WelcomeMessage родителем будет Dashboard, но владеет им App.
Производительность
Важно понимать, что ответственность за перерендеринг компонента извне несет его владелец, а не родитель.
Добавим в компонент Dashboard собственное состояние:
Если изменится состояние внутри Dashboard, компонент WelcomeMessage не будет перерендерен, несмотря на то, что он находится внутри дашборда. (Помню, как меня это удивило в первый раз).
Живой пример можно посмотреть здесь: https://codesandbox.io/p/sandbox/parents-vs-owners-fjtr35. В консоли видно, какие компоненты обновляются при изменении состояния.
Мы можем использовать этот момент, чтобы избежать ненужных перерисовок.
Вот плохой пример:
У нас есть очень тяжелый компонент, который не хотелось бы перерисовывать без необходимости. Но его владельцем является App, у которого есть состояние, которое может меняться. И хотя нашему компоненту это состояние не требуется, он будет обновляться вместе с ним.
Хороший ход - убрать это состояние из App, перенести его на уровень ниже, используя компонент со слотом, в которым можно передать тяжелый компонент.
Не злоупотребляем
Подход прекрасный, но злоупотреблять им не следует. Инверсия контроля может сделать компонент-владелец слишком сложным, а нам такое не нужно.
#паттерны #примерыкода #ссылки
Статьи (англ.):
Поток данных https://julesblom.com/writing/parents-owners-data-flow
Производительность рендеринга https://julesblom.com/writing/parents-owners-performance
Статьи очень подробно и с интерактивными примерами рассказывают о двух способах построения дерева рендеринга в React (Parent Hierarchy и Owner Hierarchy). И о том, как понимание разницы между ними позволяет нам избежать prop drilling, а также улучшить производительность рендеринга без использования
memo
.Пример
const App = () => {
const [user, setUser] = useState({ name: ‘John’ })
return <div>
<Dashboard user={user} />
</div>
}
const Dashboard = ({ user }) => {
return <div>
<WelcomeMessage user={user} />
</div>
}
const WelcomeMessage = ({ user }) => {
return <div>Hello, {user.name}</div>
}
Компонент App рендерит Dashboard, а тот в свою очередь рендерит WelcomeMessage. В компоненте App определяется стейт
user
, который используется в WelcomeMessage. Нам приходится пробрасывать его сквозь Dashboard, хотя самому дашборду он совсем не нужен.Компоненты со слотами
Ту же функциональность можно реализовать по-другому:
const App = () => {
const [user, setUser] = useState({ name: ‘John’ })
return <div>
<Dashboard>
<WelcomeMessage user={user} />
</Dashboard>
</div>
}
const Dashboard = ({ children }) => {
return <div>{children}</div>
}
Тут Dashboard реализован как компонент «со слотом». Он рендерит кусок приложения, не имея представления о том, что в нем происходит. Это инверсия контроля, когда ответственность за этот фрагмент переходит к родителю Dashboard.
Благодаря такой структуре мы избегаем пробрасывания пропсов через незаинтересованные компоненты.
На этом примере легко понять разницу между родителем (непосредственная обертка) и владельцем (компонент, который контролирует рендеринг). Для WelcomeMessage родителем будет Dashboard, но владеет им App.
Производительность
Важно понимать, что ответственность за перерендеринг компонента извне несет его владелец, а не родитель.
Добавим в компонент Dashboard собственное состояние:
const Dashboard = ({ children }) => {
const [data, setData] = useState(‘hello’)
return <div>
{data}
{children}
</div>
}
Если изменится состояние внутри Dashboard, компонент WelcomeMessage не будет перерендерен, несмотря на то, что он находится внутри дашборда. (Помню, как меня это удивило в первый раз).
Живой пример можно посмотреть здесь: https://codesandbox.io/p/sandbox/parents-vs-owners-fjtr35. В консоли видно, какие компоненты обновляются при изменении состояния.
Мы можем использовать этот момент, чтобы избежать ненужных перерисовок.
Вот плохой пример:
const App = () => {
const [counter, setCounter] = useState(1)
const increase = () => {
setCounter(prev => prev + 1)
}
return <div>
{counter}
<button onClick={increase)>Increase</button>
<VeryExpensiveComponent />
</div>
}
У нас есть очень тяжелый компонент, который не хотелось бы перерисовывать без необходимости. Но его владельцем является App, у которого есть состояние, которое может меняться. И хотя нашему компоненту это состояние не требуется, он будет обновляться вместе с ним.
Хороший ход - убрать это состояние из App, перенести его на уровень ниже, используя компонент со слотом, в которым можно передать тяжелый компонент.
const App = () => {
return <Counter>
<VeryExpensiveComponent />
</Counter>
}
Не злоупотребляем
Подход прекрасный, но злоупотреблять им не следует. Инверсия контроля может сделать компонент-владелец слишком сложным, а нам такое не нужно.
#паттерны #примерыкода #ссылки
JulesBlom.com
Parents & Owners in React: Data Flow | JulesBlom.com
There are two, similar but different component trees in React. Being aware of this difference can help you better structure your React application.
👍5🔥2
Testing Library. Вызов событий. fireEvent
В тестах часто требуется проверить как элементы реагируют на действия пользователя, например, что при клике на кнопку отправились данные формы.
Testing Library предоставляет несколько способов эмулировать такие события. Самый простой - метод
Документация нам говорит, что в большинстве случаев мы будем использовать другой пакет - @testing-library/user-event, но пока посмотрим на этот.
Метод
Также есть несколько готовых методов для конкретных событий, которым можно передать объект с настройками:
Дока тут: https://testing-library.com/docs/dom-testing-library/api-events
Это особенно полезно для сложных событий ввода, например, для инпута с файлами, которому мы не можем вручную установить значение свойства
Пример: https://codesandbox.io/p/devbox/testing-library-global-jsdom-screen-react-junior-forked-7t9cmt
#тестирование #testinglibrary #документация #примерыкода
В тестах часто требуется проверить как элементы реагируют на действия пользователя, например, что при клике на кнопку отправились данные формы.
Testing Library предоставляет несколько способов эмулировать такие события. Самый простой - метод
fireEvent
из пакета @testing-library/dom
. Документация нам говорит, что в большинстве случаев мы будем использовать другой пакет - @testing-library/user-event, но пока посмотрим на этот.
Метод
fireEvent
принимает первым аргументом элемент, на котором необходимо вызвать событие. Вторым - объект события.
fireEvent(input, new MouseEvent('click'))
Также есть несколько готовых методов для конкретных событий, которым можно передать объект с настройками:
fireEvent.change(input, { target: { value: 'hello' } })
fireEvent.keyDown(domNode, {key: 'Enter', code: 'Enter', charCode: 13})
Дока тут: https://testing-library.com/docs/dom-testing-library/api-events
Это особенно полезно для сложных событий ввода, например, для инпута с файлами, которому мы не можем вручную установить значение свойства
files
.Пример: https://codesandbox.io/p/devbox/testing-library-global-jsdom-screen-react-junior-forked-7t9cmt
#тестирование #testinglibrary #документация #примерыкода
Testing-Library
Firing Events | Testing Library
Note
👍2
Testing Library. Асинхронщина. waitFor
В ряде случаев для тестирования нам нужно подождать, когда на странице что-то произойдет: появится/пропадет конкретный элемент, закончится какой-то таймер, юзер что-то кликнет и так далее. Библиотека предоставляет несколько способов для такого ожидания.
waitFor
Основная механика ожидания - это метод waitFor. Первым аргументом он принимает коллбэк, который собственно и должен проверить, произошло ли то, что нужно. Если произошло, нужно вернуть true, если нет - выбросить исключение. В этом случае коллбэк будет вызван для проверки снова спустя некоторое время, и так до тех пор, пока он не будет удовлетворен.
Мы можем даже использовать expect для проверки условий, так как при несоответствии он как раз выбрасывает ошибку:
Вместо коллбэка-функции можно передать промис, он не будет вызываться повторно.
Вторым аргументом можно передать объект с настройками:
- container: HTMLElement - по умолчанию document
Если мы ждем появления элемента внутри конкретного контейра, то можно передать его
- timeout - время ожидания
- interval - как часто вызывать коллбэк
- onTimeout: (error: Error) => Error - по умолчанию добавляет к ошибке текущее состояние элемента container
- mutationObsereverOptions - для настройки вызова коллбэка при изменениях контейнера
#тестирование #testinglibrary #документация #примерыкода
В ряде случаев для тестирования нам нужно подождать, когда на странице что-то произойдет: появится/пропадет конкретный элемент, закончится какой-то таймер, юзер что-то кликнет и так далее. Библиотека предоставляет несколько способов для такого ожидания.
waitFor
Основная механика ожидания - это метод waitFor. Первым аргументом он принимает коллбэк, который собственно и должен проверить, произошло ли то, что нужно. Если произошло, нужно вернуть true, если нет - выбросить исключение. В этом случае коллбэк будет вызван для проверки снова спустя некоторое время, и так до тех пор, пока он не будет удовлетворен.
Мы можем даже использовать expect для проверки условий, так как при несоответствии он как раз выбрасывает ошибку:
await waitFor(() => expect(mockAPI).toHaveBeenCalledTimes(1))
Вместо коллбэка-функции можно передать промис, он не будет вызываться повторно.
Вторым аргументом можно передать объект с настройками:
- container: HTMLElement - по умолчанию document
Если мы ждем появления элемента внутри конкретного контейра, то можно передать его
- timeout - время ожидания
- interval - как часто вызывать коллбэк
- onTimeout: (error: Error) => Error - по умолчанию добавляет к ошибке текущее состояние элемента container
- mutationObsereverOptions - для настройки вызова коллбэка при изменениях контейнера
#тестирование #testinglibrary #документация #примерыкода
👍2👏1
Testing Library. Асинхронщина. findBy
Мы помним, что у нас есть три вида запросов - getBy, queryBy и findBy. Так вот findBy - это комбинация getBy и уже рассмотренного выше метода waitFor. Таким образом, запрос
Можно использовать в комбинации с await:
#тестирование #testinglibrary #документация #примерыкода
Мы помним, что у нас есть три вида запросов - getBy, queryBy и findBy. Так вот findBy - это комбинация getBy и уже рассмотренного выше метода waitFor. Таким образом, запрос
findBy
по умолчанию ожидает, когда искомый элемент появится на странице.Можно использовать в комбинации с await:
await screen.findByText('Clicked once')
#тестирование #testinglibrary #документация #примерыкода
👍1
Testing Library. Асинхронщина. waitForElementToBeRemoved
Есть еще одна утилита, позволяющая дождаться, когда конкретный элемент исчезнет со страницы: waitForElementToBeRemoved.
Первым параметром она принимает элемент или массив элементов. Кроме того, можно передать функцию-коллбэк, которая вернет элемент или массив элементов. Если элемент null или массив пустой, будет ошибка.
Вторым параметром можно передать объект с настройками, такой же как у функции waitFor.
#тестирование #testinglibrary #документация #примерыкода
Есть еще одна утилита, позволяющая дождаться, когда конкретный элемент исчезнет со страницы: waitForElementToBeRemoved.
Первым параметром она принимает элемент или массив элементов. Кроме того, можно передать функцию-коллбэк, которая вернет элемент или массив элементов. Если элемент null или массив пустой, будет ошибка.
const el = document.querySelector('div.getOuttaHere')
await waitForElementToBeRemoved(el)
Вторым параметром можно передать объект с настройками, такой же как у функции waitFor.
#тестирование #testinglibrary #документация #примерыкода
👍1
Testing Library. Размышления о fireEvent
Хорошая статья в документации про особенности эмуляции событий: https://testing-library.com/docs/guide-events
Помня о главном принципе библиотеки, что "тестировать нужно максимально похоже на то, как действует реальный пользователь", мы тем не менее должны помнить о некоторых условностях, особенно в работе с событиями.
Например, если мы вызываем метод
Про это важно помнить, чтобы не столкнуться однажды с неожиданным и непонятным поведением.
Статья предлагает пару паттернов для эмуляции событий с помощью fireEvent. Например, вместо прямого вызова события keyDown на элементе, лучше сначала сфокусироваться на нем, а затем вызвать событие на document.activeElement:
#тестирование #testinglibrary #документация #примерыкода
Хорошая статья в документации про особенности эмуляции событий: https://testing-library.com/docs/guide-events
Помня о главном принципе библиотеки, что "тестировать нужно максимально похоже на то, как действует реальный пользователь", мы тем не менее должны помнить о некоторых условностях, особенно в работе с событиями.
Например, если мы вызываем метод
fireEvent.click(element)
, он задиспатчит событие клика и сработает обработчик клика, если он есть. В большинстве случаев нам этого более чем достаточно. Однако когда настоящий юзер кликает на настоящий элемент, мы получаем гораздо больше событий: mouseOver, mouseMove, mouseDown, focus (если элемент focusable), mouseUp и только теперь, наконец, click.Про это важно помнить, чтобы не столкнуться однажды с неожиданным и непонятным поведением.
Статья предлагает пару паттернов для эмуляции событий с помощью fireEvent. Например, вместо прямого вызова события keyDown на элементе, лучше сначала сфокусироваться на нем, а затем вызвать событие на document.activeElement:
getByText('click me')
fireEvent.keyDown(document.activeElement || document.body)
#тестирование #testinglibrary #документация #примерыкода
Testing-Library
Considerations for fireEvent | Testing Library
Interactions vs. events
👍2
Testing Library. Тестирование доступности
Если возникла необходимость затестить доступность вашего приложения, Testing Library предлагает пару полезных утилит:
getRoles
Находит на странице все элементы, имеющие роли, и возвращает их в виде объекта
logRoles
То же самое, только выводит данные в консоль.
isInaccessible
Проверяет, исключен ли элемент из дерева доступности браузера.
Подробнее в документации: https://testing-library.com/docs/dom-testing-library/api-accessibility
#тестирование #testinglibrary #документация
Если возникла необходимость затестить доступность вашего приложения, Testing Library предлагает пару полезных утилит:
getRoles
Находит на странице все элементы, имеющие роли, и возвращает их в виде объекта
logRoles
То же самое, только выводит данные в консоль.
isInaccessible
Проверяет, исключен ли элемент из дерева доступности браузера.
Подробнее в документации: https://testing-library.com/docs/dom-testing-library/api-accessibility
#тестирование #testinglibrary #документация
Testing-Library
Accessibility | Testing Library
Testing for Accessibility
👍1