⚽ Одна обёртка, две стороны сетки: турнирная таблица на subgrid
Турнирную сетку плей-офф рисуют симметрично: часть пар раунда слева, часть справа. На сайте FIFA каждая такая половина свёрстана отдельным div, и разметка просто повторяет картинку.
Есть вариант чище. Раунд остаётся одним элементом с детьми .start и .end. Он растягивается на всю общую сетку .rounds и через subgrid получает её колонки и строки:
Теперь .start встаёт в первую колонку, а .end в последнюю, хотя в DOM они соседи.
Подвох в выравнивании: align-items: center не ставит матчи соседних раундов друг напротив друга. Помогает flex: 1 у каждого матча внутри flex-колонок .start и .end, чтобы матчи делили высоту поровну.
А в статье видно, как та же разметка на узком экране перестраивается так, что каждый раунд занимает одну колонку.
👉 @seniorFront
Турнирную сетку плей-офф рисуют симметрично: часть пар раунда слева, часть справа. На сайте FIFA каждая такая половина свёрстана отдельным div, и разметка просто повторяет картинку.
Есть вариант чище. Раунд остаётся одним элементом с детьми .start и .end. Он растягивается на всю общую сетку .rounds и через subgrid получает её колонки и строки:
.round-of-8 {
grid-column: 1 / -1;
grid-row: 1 / -1;
display: grid;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
}Теперь .start встаёт в первую колонку, а .end в последнюю, хотя в DOM они соседи.
Подвох в выравнивании: align-items: center не ставит матчи соседних раундов друг напротив друга. Помогает flex: 1 у каждого матча внутри flex-колонок .start и .end, чтобы матчи делили высоту поровну.
А в статье видно, как та же разметка на узком экране перестраивается так, что каждый раунд занимает одну колонку.
👉 @seniorFront
👍1
🎉 iframe, который сам подстраивается под содержимое
Обычно у <iframe> фиксированный размер. Встроенная форма или виджет комментариев выросли, и появляется полоса прокрутки.
В Chrome 154 фрейм умеет брать размер из встроенного документа. Нужно согласие обеих сторон. Внешняя страница задаёт фрейму свойство frame-sizing:
А документ внутри фрейма кладёт в head мета-тег
Размер передаётся после загрузки. Если содержимое изменилось, документ вызывает
Пока это есть только в Chrome 154, в Firefox и Safari поддержки нет.
В статье есть демо: многошаговая форма во фрейме, которая меняет размер на каждом шаге без единой полосы прокрутки.
👉 @seniorFront
Обычно у <iframe> фиксированный размер. Встроенная форма или виджет комментариев выросли, и появляется полоса прокрутки.
В Chrome 154 фрейм умеет брать размер из встроенного документа. Нужно согласие обеих сторон. Внешняя страница задаёт фрейму свойство frame-sizing:
/* In styles.css for index.html */
iframe {
frame-sizing: content-height;
width: 100%;
}
А документ внутри фрейма кладёт в head мета-тег
responsive-embedded-sizing. Его параметр allow-origins ограничивает, каким сайтам можно сообщать размер.Размер передаётся после загрузки. Если содержимое изменилось, документ вызывает
window.requestResize().Пока это есть только в Chrome 154, в Firefox и Safari поддержки нет.
В статье есть демо: многошаговая форма во фрейме, которая меняет размер на каждом шаге без единой полосы прокрутки.
👉 @seniorFront
❤3🔥1
✨ Одна и та же CSS-переменная может дать 1, а может 2
Кастомные свойства вроде --index по умолчанию хранятся как кусок текста. Вычисляется он там, где переменную подставили через var(), а не там, где объявили.
Пример Джейка Арчибальда. У второго div задано --index: sibling-index() (номер элемента среди соседей, с единицы). Его первый ребёнок, абзац, получает scale: var(--index). Масштаб будет 1: функция считается уже для абзаца.
Теперь зарегистрируем свойство с типом:
Значение вычислится прямо на div и станет числом 2, абзац увеличится вдвое. С em, lh и единицами контейнерных запросов то же правило.
Но var() внутри значения подставляется сразу при любом типе. Если в --index есть var(--multiplier), переопределять --multiplier у ребёнка бесполезно.
А что при этом происходит с URL из разных таблиц стилей, разобрано в статье.
👉 @seniorFront
Кастомные свойства вроде --index по умолчанию хранятся как кусок текста. Вычисляется он там, где переменную подставили через var(), а не там, где объявили.
Пример Джейка Арчибальда. У второго div задано --index: sibling-index() (номер элемента среди соседей, с единицы). Его первый ребёнок, абзац, получает scale: var(--index). Масштаб будет 1: функция считается уже для абзаца.
Теперь зарегистрируем свойство с типом:
@property --index { syntax: '<number>'; inherits: true; initial-value: 0;}Значение вычислится прямо на div и станет числом 2, абзац увеличится вдвое. С em, lh и единицами контейнерных запросов то же правило.
Но var() внутри значения подставляется сразу при любом типе. Если в --index есть var(--multiplier), переопределять --multiplier у ребёнка бесполезно.
А что при этом происходит с URL из разных таблиц стилей, разобрано в статье.
👉 @seniorFront
📜 Страница скроллится, а window.scrollY показывает 0
У каждой страницы есть главный контейнер прокрутки, root scroller. Только он получает восстановление позиции при возврате «назад», прокрутку пробелом и стрелками, сворачивание адресной строки на телефоне.
Потерять его легко. Вот частая комбинация:
Из-за overflow-x: hidden по вертикали у body получается auto. А height: 100% делает body ровно в высоту экрана. Теперь прокручивается body, а не страница. Внешне всё как раньше, ошибок нет.
Проверить просто: прокрутите вниз и выполните в консоли
Вложенные контейнеры прокрутки сами по себе нормальны, например для сайдбара или чата. Проблема, только когда такой контейнер занимает весь экран.
В статье разобрано, как сделать липкую шапку, футер внизу и блокировку прокрутки под модалкой, не теряя root scroller.
👉 @seniorFront
У каждой страницы есть главный контейнер прокрутки, root scroller. Только он получает восстановление позиции при возврате «назад», прокрутку пробелом и стрелками, сворачивание адресной строки на телефоне.
Потерять его легко. Вот частая комбинация:
html,
body {
height: 100%;
overflow-x: hidden;
}
Из-за overflow-x: hidden по вертикали у body получается auto. А height: 100% делает body ровно в высоту экрана. Теперь прокручивается body, а не страница. Внешне всё как раньше, ошибок нет.
Проверить просто: прокрутите вниз и выполните в консоли
window.scrollY. Ноль значит, что скроллится что-то другое.Вложенные контейнеры прокрутки сами по себе нормальны, например для сайдбара или чата. Проблема, только когда такой контейнер занимает весь экран.
В статье разобрано, как сделать липкую шапку, футер внизу и блокировку прокрутки под модалкой, не теряя root scroller.
👉 @seniorFront
❤1
📐 Как заменить CSS progress(), пока его поддерживают не везде
Функция progress() показывает, где значение лежит между двумя границами, и возвращает число от 0 до 1. Формула простая: (значение - начало) / (конец - начало).
Но поделить 180px на 880px и получить просто число умеют не все браузеры: Firefox, например, пока нет. Выручает tan(atan2()). Функция atan2() по сути сама делит первый аргумент на второй, а tan() отдаёт обычное число без единиц.
Так мы узнаём, где 500px между 320px и 1200px. Чтобы результат не выходил за пределы от 0 до 1, как у progress(), оберните его в clamp().
С разными единицами, например vw и px, трюк кроссбраузерно не сработает. Зарегистрируйте ширину экрана через @property как <length>, и браузер сам переведёт её в пиксели.
Как быть, если границы заданы в rem, и какой вариант выбрать под ваши единицы, автор разбирает в статье.
👉 @seniorFront
Функция progress() показывает, где значение лежит между двумя границами, и возвращает число от 0 до 1. Формула простая: (значение - начало) / (конец - начало).
Но поделить 180px на 880px и получить просто число умеют не все браузеры: Firefox, например, пока нет. Выручает tan(atan2()). Функция atan2() по сути сама делит первый аргумент на второй, а tan() отдаёт обычное число без единиц.
--progress: tan(atan2(500px - 320px, 1200px - 320px));
Так мы узнаём, где 500px между 320px и 1200px. Чтобы результат не выходил за пределы от 0 до 1, как у progress(), оберните его в clamp().
С разными единицами, например vw и px, трюк кроссбраузерно не сработает. Зарегистрируйте ширину экрана через @property как <length>, и браузер сам переведёт её в пиксели.
Как быть, если границы заданы в rem, и какой вариант выбрать под ваши единицы, автор разбирает в статье.
👉 @seniorFront
❤1
🔑 Обработчик postMessage без проверок слушается кого угодно
Виджет в iframe (комментарии, форма оплаты) не может сам растянуться на странице. Поэтому он шлёт родителю сообщение через postMessage, а родитель в обработчике message выставляет iframe новую высоту.
Подвох в том, что браузер доставит сообщение от любого окна, у которого есть ссылка на ваше. Чужая страница одной строкой пришлёт
Спасает строгое сравнение
Но origin говорит, с какого сайта пришло сообщение, а не из какого окна. Два iframe с одного домена пройдут проверку оба, и сообщение от одного изменит размер другого.
Как это закрыть через
👉 @seniorFront
Виджет в iframe (комментарии, форма оплаты) не может сам растянуться на странице. Поэтому он шлёт родителю сообщение через postMessage, а родитель в обработчике message выставляет iframe новую высоту.
Подвох в том, что браузер доставит сообщение от любого окна, у которого есть ссылка на ваше. Чужая страница одной строкой пришлёт
{ type: 'resize', height: 1 }, и виджет схлопнется до одного пикселя.Спасает строгое сравнение
event.origin с ожидаемым адресом. Это поле заполняет сам браузер, и отправитель не может его подделать.if (event.origin !== WIDGET_ORIGIN) return;
Но origin говорит, с какого сайта пришло сообщение, а не из какого окна. Два iframe с одного домена пройдут проверку оба, и сообщение от одного изменит размер другого.
Как это закрыть через
event.source и что ещё проверить в самих данных (вроде height: 1e9), автор показывает на живых демо с тремя атаками.👉 @seniorFront
🔍 Почему overflow: hidden не удерживает размытие
Свойство backdrop-filter: blur() размывает всё, что видно сквозь полупрозрачный элемент. Так делают эффект матового стекла. Снимок фона браузер берёт из готовой GPU-текстуры (compositing layer), а не из DOM. Кто чей родитель, ему неважно.
В статье есть живой пример. Карточки с blur(70px) лежат в scroll-контейнере с overflow: hidden, а снаружи стоит яркая кнопка PLAY. Карточка подъезжает к кнопке ближе чем на 70px и начинает светиться её цветом.
Дело в том, что overflow: hidden обрезает на этапе paint, а размытие работает позже, на этапе composite. Помогает
Только помните, что clip-path обрежет всё содержимое контейнера, включая дочерние элементы, которые выходят за его край.
В статье ещё три бага в Safari и на iPhone и разбор, почему большой радиус blur так дорого обходится GPU.
👉 @seniorFront
Свойство backdrop-filter: blur() размывает всё, что видно сквозь полупрозрачный элемент. Так делают эффект матового стекла. Снимок фона браузер берёт из готовой GPU-текстуры (compositing layer), а не из DOM. Кто чей родитель, ему неважно.
В статье есть живой пример. Карточки с blur(70px) лежат в scroll-контейнере с overflow: hidden, а снаружи стоит яркая кнопка PLAY. Карточка подъезжает к кнопке ближе чем на 70px и начинает светиться её цветом.
Дело в том, что overflow: hidden обрезает на этапе paint, а размытие работает позже, на этапе composite. Помогает
clip-path: inset(0) на контейнере: автор называет его единственным надёжным способом.Только помните, что clip-path обрежет всё содержимое контейнера, включая дочерние элементы, которые выходят за его край.
В статье ещё три бага в Safari и на iPhone и разбор, почему большой радиус blur так дорого обходится GPU.
👉 @seniorFront
❤1
🔗 Линия между двумя блоками без JavaScript
Линия между двумя карточками сама тянется за ними, когда они двигаются. Скрипт для расчёта координат не нужен.
Это CSS Anchor Positioning: элемент с anchor-name становится якорем, а блок с position: absolute привязывает к нему свои стороны через anchor(), даже при разных родителях.
Главный трюк: привязать противоположные стороны блока к двум разным якорям, и он растянется между ними:
В фон кладём SVG с диагональю и background-size: 100% 100%. В самом SVG нужен preserveAspectRatio="none", иначе линия не растянется по обеим осям.
👉 @seniorFront
Линия между двумя карточками сама тянется за ними, когда они двигаются. Скрипт для расчёта координат не нужен.
Это CSS Anchor Positioning: элемент с anchor-name становится якорем, а блок с position: absolute привязывает к нему свои стороны через anchor(), даже при разных родителях.
Главный трюк: привязать противоположные стороны блока к двум разным якорям, и он растянется между ними:
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.route--one {
position: absolute;
left: anchor(--a1 right);
right: anchor(--a2 left);
top: anchor(--a1 center);
bottom: anchor(--a2 center);
}В фон кладём SVG с диагональю и background-size: 100% 100%. В самом SVG нужен preserveAspectRatio="none", иначе линия не растянется по обеим осям.
👉 @seniorFront
🚀 Два атрибута у картинки, и страница перестаёт прыгать
Пока файл картинки грузится, тег
Лечится это заранее заданными пропорциями. У тега указываем
В демо-замере из статьи это сэкономило около 12% процессорного времени.
Не забудьте про
В статье ещё разобраны старые трюки с padding-bottom и aspect-ratio и объяснено, почему они проигрывают простым атрибутам.
👉 @seniorFront
Пока файл картинки грузится, тег
img занимает на странице ноль места. Потом картинка приходит, блок раздвигается, и текст под ним резко уезжает вниз. Браузер тратит процессорное время на пересчёт раскладки, а читатель теряет строку, которую читал.Лечится это заранее заданными пропорциями. У тега указываем
width=800 height=600, а в CSS пишем width: 100% и height: auto. Картинка растянется на ширину контейнера, а высоту браузер вычислит из пропорции 800×600 ещё до загрузки файла. Место зарезервировано, ничего не дёргается.В демо-замере из статьи это сэкономило около 12% процессорного времени.
Не забудьте про
height: auto: без него картинки растягиваются неправильно.В статье ещё разобраны старые трюки с padding-bottom и aspect-ratio и объяснено, почему они проигрывают простым атрибутам.
👉 @seniorFront
👍2🔥1
🪄 Один URL: из списка открывается modal, по прямой ссылке целая страница
Если modal с деталями хранится только в useState, адрес не меняется. Ссылкой не поделиться, после reload снова список.
В Next.js App Router можно иначе. Страница events/[id] работает сама по себе. А рядом в layout живёт слот @modal (в URL он не попадает) с перехватчиком того же адреса:
Префикс (.) значит «перехватить events/[id] на том же уровне». Переход по Link без перезагрузки перехватывается, и детали рисуются поверх списка. Прямая ссылка или reload перехват не включают: открывается полная страница с metadata и canonical.
Подвох: слоту нужен default.tsx, возвращающий null. Без него при прямом открытии @modal остаётся без маршрута, и можно получить 404.
В статье разобрано, как после закрытия вернуть пользователя на ту же страницу поиска со всеми фильтрами.
👉 @seniorFront
Если modal с деталями хранится только в useState, адрес не меняется. Ссылкой не поделиться, после reload снова список.
В Next.js App Router можно иначе. Страница events/[id] работает сама по себе. А рядом в layout живёт слот @modal (в URL он не попадает) с перехватчиком того же адреса:
@modal/
(.)events/
[id]/
page.tsx
Префикс (.) значит «перехватить events/[id] на том же уровне». Переход по Link без перезагрузки перехватывается, и детали рисуются поверх списка. Прямая ссылка или reload перехват не включают: открывается полная страница с metadata и canonical.
Подвох: слоту нужен default.tsx, возвращающий null. Без него при прямом открытии @modal остаётся без маршрута, и можно получить 404.
В статье разобрано, как после закрытия вернуть пользователя на ту же страницу поиска со всеми фильтрами.
👉 @seniorFront
👍1
По данным Минфин 32% граждан покинули своё место работы поскольку уже в первый месяц смогли заработать 250 000 рублей, а через 2-3 месяца с легкостью вышли на семизначные числа.
Самое смешное, что лишь 8% россиян что-то слышали об этой профессии хотя все инструкции бесплатно лежат на канале Арбитраж Криптовалют
Там объясняют, как имея телефон и доступ в интернет заработать свои первые $3000. Причём разжёвывают так доступно, что поймёт даже ребенок. Кстати, на канале сегодня вышла новая рабочая связка!
Ссылка для входа — https://t.me/+GY-3aS-FN5c0Yjhi
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2👎2❤1🔥1
АЙТИШНИКИ БЕСПЛАТНОЕ ОБУЧЕНИЕ
Проект «Terminal» стал крупнейшей библиотекой бесплатного образования. В одном канале собраны курсы, книги, полезные инструменты и практические тренажёры для всех разработчиков:
• Практические курсы и задания
• Книги и статьи известных авторов
• Полезные инструменты и ресурсы
• IT-новости и инсайды
Обучение по всем направлениям: SQL, Python, ML, Frontend, PHP, C++, Go, Git, Linux, QA, Java, Vibe-coding, InfoSec и др.
⌨️ подписаться
Проект «Terminal» стал крупнейшей библиотекой бесплатного образования. В одном канале собраны курсы, книги, полезные инструменты и практические тренажёры для всех разработчиков:
• Практические курсы и задания
• Книги и статьи известных авторов
• Полезные инструменты и ресурсы
• IT-новости и инсайды
Обучение по всем направлениям: SQL, Python, ML, Frontend, PHP, C++, Go, Git, Linux, QA, Java, Vibe-coding, InfoSec и др.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
✨ Подсветка текущего раздела в оглавлении без JavaScript
На длинной странице оглавление может подсвечивать раздел, который вы сейчас читаете. Этот приём зовут scrollspy, и обычно он держится на JavaScript, например на IntersectionObserver.
Новое CSS-свойство scroll-target-group делает это без скриптов. Ставим его на обёртку с якорными ссылками:
Теперь браузер сам связывает каждую ссылку с её разделом. Ссылка на раздел, который сейчас на экране, получает псевдокласс
Пока это работает только в браузерах на Chromium, Firefox и Safari не поддерживают. Но там остаётся обычный список ссылок, так что навигация не ломается.
В статье к этому добавляют линию, которая плавно доезжает до текущего пункта через anchor positioning.
👉 @seniorFront
На длинной странице оглавление может подсвечивать раздел, который вы сейчас читаете. Этот приём зовут scrollspy, и обычно он держится на JavaScript, например на IntersectionObserver.
Новое CSS-свойство scroll-target-group делает это без скриптов. Ставим его на обёртку с якорными ссылками:
.toc {
scroll-target-group: auto;
}Теперь браузер сам связывает каждую ссылку с её разделом. Ссылка на раздел, который сейчас на экране, получает псевдокласс
:target-current, и её остаётся только покрасить. Для пунктов выше и ниже есть :target-before и :target-after.Пока это работает только в браузерах на Chromium, Firefox и Safari не поддерживают. Но там остаётся обычный список ссылок, так что навигация не ломается.
В статье к этому добавляют линию, которая плавно доезжает до текущего пункта через anchor positioning.
👉 @seniorFront
👍1
🔍 Chrome ругается «Blocked aria-hidden»? Не глушите его
Обычно это всплывает при закрытии модалки. Окно уже помечено
Популярный фикс
Чинится это порядком действий. Сначала фокус возвращается на кнопку, которая открыла окно, и только потом окно прячется. На время анимации закрытия окну ставят
Подвох в том, что
В статье разобраны четыре сценария этой ошибки и готовый код для чистого JS и React.
👉 @seniorFront
Обычно это всплывает при закрытии модалки. Окно уже помечено
aria-hidden="true", а фокус ещё на кнопке «Закрыть» внутри. Атрибут прячет элемент от скринридера, но не от Tab, и фокус застревает там, чего для скринридера «нет».Популярный фикс
blur() чистит консоль, но роняет фокус на <body>. Пользователь скринридера слышит тишину и листает Tab с самого начала страницы.Чинится это порядком действий. Сначала фокус возвращается на кнопку, которая открыла окно, и только потом окно прячется. На время анимации закрытия окну ставят
inert: оно выпадает из Tab, и скринридер его не видит.Подвох в том, что
inert блокирует и фокус. Если фон за окном был inert, снимите его до focus(), иначе фокус молча не переедет.В статье разобраны четыре сценария этой ошибки и готовый код для чистого JS и React.
👉 @seniorFront
❤1
🤖Podlodka Backend Crew: AI в бэкенд-задачах
Демо с AI чаще всего выглядят одинаково: агент за десять минут пишет сервис с нуля.
Вот только в работе бэкендера всё сложнее: легаси, рискованные миграции, неработающие тесты и ревью, которое становится бутылочным горлышком.
🗓 С 26 по 30 октября в сезоне Podlodka Backend Crew «AI в бэкенд-задачах» участники:
• Научатся передавать агенту инженерные стандарты и правила, чтобы он писал так, как нужно, а не как получится
• Соберут ревью, где несколько моделей независимо проверяют изменения, а финальное решение остаётся за человеком
• Посмотрят, куда переезжает сложность миграции легаси, когда код пишет агент
• Выяснят, где ломается мечта отдать миграцию агенту целиком
• Разберут, какие антипаттерны тестов агенты уже успели перенять от разработчиков.
Формат — пять дней живых Zoom-сессий утром и вечером, закрытое сообщество в Telegram и общение со спикерами.
Будет много интерактива и практики. Базовый ликбез оставят за бортом — этот сезон для тех, кто уже пробует AI в реальных проектах. Присоединяйтесь с 26 октября!
🔗 Билеты и программа: https://podlodka.io/becrew
Демо с AI чаще всего выглядят одинаково: агент за десять минут пишет сервис с нуля.
Вот только в работе бэкендера всё сложнее: легаси, рискованные миграции, неработающие тесты и ревью, которое становится бутылочным горлышком.
• Научатся передавать агенту инженерные стандарты и правила, чтобы он писал так, как нужно, а не как получится
• Соберут ревью, где несколько моделей независимо проверяют изменения, а финальное решение остаётся за человеком
• Посмотрят, куда переезжает сложность миграции легаси, когда код пишет агент
• Выяснят, где ломается мечта отдать миграцию агенту целиком
• Разберут, какие антипаттерны тестов агенты уже успели перенять от разработчиков.
Формат — пять дней живых Zoom-сессий утром и вечером, закрытое сообщество в Telegram и общение со спикерами.
Будет много интерактива и практики. Базовый ликбез оставят за бортом — этот сезон для тех, кто уже пробует AI в реальных проектах. Присоединяйтесь с 26 октября!
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
🧩 Компонент рисует, хук думает
В React-компоненте разметка часто перемешана с подготовкой данных: проверками, вычислениями, обработкой ошибок. JSX от этого читается тяжело.
Статья на Хабре предлагает идею, подсмотренную во Vue 3: вынести подготовку в хук с именем useSetup[Component]. Для панели профиля ProfilePanel это useSetupProfilePanel. Он отдаёт флаг isLoading и готовые пропсы для карточки, а в компоненте остаётся только разметка.
Тесты от этого тоже упрощаются. В тесте компонента хук подменяется моком (vi.mock из Vitest), и проверяется одно: попали ли пропсы из хука в нужный элемент. Логику самого хука проверяют его собственные тесты.
Важно понимать, что shallow-тест через Shallowly (библиотеку автора статьи) не делает полный рендер. Дочерний компонент не выполняется, поэтому клики и изменения DOM проверяет отдельный DOM-тест.
В статье весь путь от исходного компонента до шести тестов и репозиторий, чтобы запустить их у себя.
👍 - если было полезно
👉 @seniorFront
В React-компоненте разметка часто перемешана с подготовкой данных: проверками, вычислениями, обработкой ошибок. JSX от этого читается тяжело.
Статья на Хабре предлагает идею, подсмотренную во Vue 3: вынести подготовку в хук с именем useSetup[Component]. Для панели профиля ProfilePanel это useSetupProfilePanel. Он отдаёт флаг isLoading и готовые пропсы для карточки, а в компоненте остаётся только разметка.
Тесты от этого тоже упрощаются. В тесте компонента хук подменяется моком (vi.mock из Vitest), и проверяется одно: попали ли пропсы из хука в нужный элемент. Логику самого хука проверяют его собственные тесты.
Важно понимать, что shallow-тест через Shallowly (библиотеку автора статьи) не делает полный рендер. Дочерний компонент не выполняется, поэтому клики и изменения DOM проверяет отдельный DOM-тест.
В статье весь путь от исходного компонента до шести тестов и репозиторий, чтобы запустить их у себя.
👍 - если было полезно
👉 @seniorFront