@sberbusiness/triplex-next-mcp-server 0.24.0-beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -0
- package/bundle/.gitkeep +0 -0
- package/bundle/components/AbstractTree.md +198 -0
- package/bundle/components/AccordionBase.md +227 -0
- package/bundle/components/Alert.md +317 -0
- package/bundle/components/Amount.md +225 -0
- package/bundle/components/AmountField.md +289 -0
- package/bundle/components/Avatar.md +209 -0
- package/bundle/components/Badge.md +277 -0
- package/bundle/components/Body.md +167 -0
- package/bundle/components/Button.md +230 -0
- package/bundle/components/ButtonDropdown.md +159 -0
- package/bundle/components/ButtonIcon.md +122 -0
- package/bundle/components/Calendar.md +445 -0
- package/bundle/components/Card.md +374 -0
- package/bundle/components/CarouselExtended.md +250 -0
- package/bundle/components/Checkbox.md +126 -0
- package/bundle/components/CheckboxTree.md +280 -0
- package/bundle/components/CheckboxTreeExtended.md +321 -0
- package/bundle/components/CheckboxXGroup.md +68 -0
- package/bundle/components/CheckboxYGroup.md +67 -0
- package/bundle/components/Chip.md +404 -0
- package/bundle/components/ChipGroup.md +179 -0
- package/bundle/components/Col.md +221 -0
- package/bundle/components/CollapsableTree.md +255 -0
- package/bundle/components/Confirm.md +343 -0
- package/bundle/components/DateField.md +371 -0
- package/bundle/components/DatePickerExtended.md +342 -0
- package/bundle/components/DateRange.md +275 -0
- package/bundle/components/DesignTokens.md +385 -0
- package/bundle/components/Divider.md +196 -0
- package/bundle/components/DocumentNumberEdit.md +280 -0
- package/bundle/components/Dropdown.md +433 -0
- package/bundle/components/Ellipsis.md +208 -0
- package/bundle/components/EmptyView.md +99 -0
- package/bundle/components/ExpandAnimation.md +281 -0
- package/bundle/components/Footer.md +339 -0
- package/bundle/components/FormField.md +326 -0
- package/bundle/components/FormGroup.md +99 -0
- package/bundle/components/Gap.md +113 -0
- package/bundle/components/Header.md +280 -0
- package/bundle/components/HelpBox.md +154 -0
- package/bundle/components/IconWrapper.md +145 -0
- package/bundle/components/Island.md +197 -0
- package/bundle/components/IslandAccordion.md +230 -0
- package/bundle/components/IslandWidget.md +233 -0
- package/bundle/components/KeyDownListener.md +157 -0
- package/bundle/components/LightBox.md +322 -0
- package/bundle/components/Link.md +161 -0
- package/bundle/components/List.md +247 -0
- package/bundle/components/ListMaster.md +204 -0
- package/bundle/components/Loader.md +161 -0
- package/bundle/components/LoaderScreen.md +134 -0
- package/bundle/components/Marker.md +127 -0
- package/bundle/components/MarkerStatus.md +148 -0
- package/bundle/components/MediaWidth.md +169 -0
- package/bundle/components/MobileView.md +120 -0
- package/bundle/components/ModalWindow.md +308 -0
- package/bundle/components/MonthYearField.md +168 -0
- package/bundle/components/MultiselectField.md +213 -0
- package/bundle/components/Notification.md +233 -0
- package/bundle/components/NumberField.md +178 -0
- package/bundle/components/OrderedList.md +169 -0
- package/bundle/components/Overlay.md +237 -0
- package/bundle/components/Page.md +246 -0
- package/bundle/components/Pagination.md +298 -0
- package/bundle/components/Portal.md +119 -0
- package/bundle/components/Radio.md +174 -0
- package/bundle/components/Row.md +125 -0
- package/bundle/components/SMSField.md +160 -0
- package/bundle/components/SegmentedControl.md +235 -0
- package/bundle/components/SelectExtendedField.md +322 -0
- package/bundle/components/SelectField.md +213 -0
- package/bundle/components/Skeleton.md +129 -0
- package/bundle/components/Slider.md +157 -0
- package/bundle/components/SliderExtended.md +210 -0
- package/bundle/components/SliderRange.md +160 -0
- package/bundle/components/SmallInput.md +101 -0
- package/bundle/components/Spoiler.md +121 -0
- package/bundle/components/StatusTracker.md +151 -0
- package/bundle/components/Step.md +121 -0
- package/bundle/components/Stepper.md +163 -0
- package/bundle/components/Suggest.md +160 -0
- package/bundle/components/SuggestField.md +157 -0
- package/bundle/components/SwipeableArea.md +144 -0
- package/bundle/components/Table.md +199 -0
- package/bundle/components/Tabs.md +152 -0
- package/bundle/components/TabsExtended.md +273 -0
- package/bundle/components/TabsLine.md +189 -0
- package/bundle/components/Tag.md +135 -0
- package/bundle/components/TagColor.md +121 -0
- package/bundle/components/TagGroup.md +100 -0
- package/bundle/components/TextField.md +152 -0
- package/bundle/components/TextareaField.md +124 -0
- package/bundle/components/ThemeProvider.md +181 -0
- package/bundle/components/Tooltip.md +254 -0
- package/bundle/components/TopOverlay.md +145 -0
- package/bundle/components/TreeView.md +224 -0
- package/bundle/components/Triggers.md +121 -0
- package/bundle/guides/codestyle.md +205 -0
- package/bundle/guides/commits.md +166 -0
- package/bundle/guides/context.md +277 -0
- package/bundle/guides/stories.md +618 -0
- package/bundle/guides/template.md +154 -0
- package/bundle/guides/tests.md +301 -0
- package/bundle/icons.json +4156 -0
- package/bundle/manifest.json +796 -0
- package/bundle/release-notes/1.1.0.mdx +26 -0
- package/bundle/release-notes/1.1.1.mdx +14 -0
- package/bundle/release-notes/1.10.0.mdx +43 -0
- package/bundle/release-notes/1.11.0.mdx +19 -0
- package/bundle/release-notes/1.12.0.mdx +21 -0
- package/bundle/release-notes/1.13.0.mdx +43 -0
- package/bundle/release-notes/1.14.0.mdx +29 -0
- package/bundle/release-notes/1.15.0.mdx +56 -0
- package/bundle/release-notes/1.16.0.mdx +55 -0
- package/bundle/release-notes/1.17.0.mdx +15 -0
- package/bundle/release-notes/1.18.0.mdx +24 -0
- package/bundle/release-notes/1.19.0.mdx +48 -0
- package/bundle/release-notes/1.19.1.mdx +9 -0
- package/bundle/release-notes/1.2.0.mdx +80 -0
- package/bundle/release-notes/1.20.0.mdx +48 -0
- package/bundle/release-notes/1.21.0.mdx +66 -0
- package/bundle/release-notes/1.22.0.mdx +75 -0
- package/bundle/release-notes/1.23.0.mdx +50 -0
- package/bundle/release-notes/1.24.0.mdx +59 -0
- package/bundle/release-notes/1.25.0.mdx +52 -0
- package/bundle/release-notes/1.26.0.mdx +11 -0
- package/bundle/release-notes/1.3.0.mdx +21 -0
- package/bundle/release-notes/1.4.0.mdx +37 -0
- package/bundle/release-notes/1.5.0.mdx +24 -0
- package/bundle/release-notes/1.6.0.mdx +37 -0
- package/bundle/release-notes/1.6.1.mdx +11 -0
- package/bundle/release-notes/1.7.0.mdx +29 -0
- package/bundle/release-notes/1.8.0.mdx +38 -0
- package/bundle/release-notes/1.9.0.mdx +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +324 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ComponentName
|
|
3
|
+
category: Category
|
|
4
|
+
figma-node: "NODE_ID"
|
|
5
|
+
figma-file: "https://www.figma.com/design/FILE_KEY/..."
|
|
6
|
+
related: []
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/Category/ComponentName.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<!--
|
|
13
|
+
ИНСТРУКЦИЯ ДЛЯ ЗАПОЛНЕНИЯ (удали этот блок после заполнения)
|
|
14
|
+
|
|
15
|
+
Этот шаблон заполняется разработчиком вместе с AI-агентом после изучения компонента.
|
|
16
|
+
Цель: дать агенту точный контекст для будущих изменений компонента.
|
|
17
|
+
|
|
18
|
+
Не копируй TypeScript-типы целиком — они читаются из исходного кода.
|
|
19
|
+
Описывай намерения и ограничения, которые не видны из кода.
|
|
20
|
+
|
|
21
|
+
Frontmatter:
|
|
22
|
+
- figma-node: raw-значение параметра `node-id` из URL Figma, например `1-328`
|
|
23
|
+
- figma-file: URL файла / design-ссылка, из которой при необходимости можно получить file key
|
|
24
|
+
- tokens: список CSS-переменных, которые компонент использует
|
|
25
|
+
- related: список связанных компонентов (строки, имена)
|
|
26
|
+
|
|
27
|
+
Таблица Stories: колонка `Example file` обязательна — её читает сборщик
|
|
28
|
+
mcp-data.json, чтобы инлайнить код примера. См. раздел Stories ниже.
|
|
29
|
+
-->
|
|
30
|
+
|
|
31
|
+
# ComponentName
|
|
32
|
+
|
|
33
|
+
## Назначение
|
|
34
|
+
|
|
35
|
+
<!-- Одно-два предложения: что делает компонент и когда его использовать. -->
|
|
36
|
+
|
|
37
|
+
Компонент для...
|
|
38
|
+
|
|
39
|
+
Используй когда: ...
|
|
40
|
+
Не используй когда: ...
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Варианты и props
|
|
45
|
+
|
|
46
|
+
<!-- Перечисли смысловые группы props. Не дублируй TypeScript — опиши намерения и ограничения. -->
|
|
47
|
+
|
|
48
|
+
### Обязательные props
|
|
49
|
+
|
|
50
|
+
| Prop | Тип | Описание |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `theme` | `EComponentTheme` | Визуальный стиль компонента |
|
|
53
|
+
| `size` | `EComponentSize` | Размер: SM (28px) / MD (40px) / LG (56px) |
|
|
54
|
+
|
|
55
|
+
### Опциональные props
|
|
56
|
+
|
|
57
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `disabled` | `boolean` | `false` | Блокирует взаимодействие |
|
|
60
|
+
|
|
61
|
+
### Ограничения по темам
|
|
62
|
+
|
|
63
|
+
<!-- Если некоторые props недоступны для определённых тем — опиши здесь. -->
|
|
64
|
+
|
|
65
|
+
- `loading` недоступен для темы `LINK`
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Дизайн-токены
|
|
70
|
+
|
|
71
|
+
<!-- Только те CSS-переменные, которые непосредственно используются в стилях компонента. -->
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
--triplex-next-ComponentName-Variant_Background_Default
|
|
75
|
+
--triplex-next-ComponentName-Variant_Color_Default
|
|
76
|
+
--triplex-next-ComponentName-Variant_Background_Hover
|
|
77
|
+
--triplex-next-ComponentName-Variant_Color_Hover
|
|
78
|
+
--triplex-next-ComponentName-Variant_Background_Active
|
|
79
|
+
--triplex-next-ComponentName-Variant_Color_Active
|
|
80
|
+
--triplex-next-ComponentName-Variant_Shadow_Focus
|
|
81
|
+
--triplex-next-ComponentName-Variant_Background_Disabled
|
|
82
|
+
--triplex-next-ComponentName-Variant_Color_Disabled
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Инварианты
|
|
88
|
+
|
|
89
|
+
<!-- Что НЕЛЬЗЯ изменять без явного обсуждения с командой. -->
|
|
90
|
+
|
|
91
|
+
- `forwardRef` на компоненте — не убирать
|
|
92
|
+
- Prop `X` изменить нельзя — он часть публичного API с версии N
|
|
93
|
+
- CSS-класс `Y` используется в тестах — не переименовывать
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Accessibility
|
|
98
|
+
|
|
99
|
+
<!-- Заполни, если компонент имеет нетривиальное поведение с точки зрения доступности. -->
|
|
100
|
+
|
|
101
|
+
<!--
|
|
102
|
+
Примеры того, что стоит описать:
|
|
103
|
+
- Управление фокусом: при открытии фокус уходит в контент, при закрытии — возвращается на триггер
|
|
104
|
+
- ARIA-роль компонента (combobox, listbox, dialog, alertdialog, tooltip...)
|
|
105
|
+
- Обязательные aria-атрибуты (aria-expanded, aria-haspopup, aria-controls, aria-labelledby...)
|
|
106
|
+
- Keyboard navigation: какие клавиши обрабатываются и что делают
|
|
107
|
+
- aria-live region, если компонент динамически обновляет содержимое
|
|
108
|
+
- Какие aria-атрибуты потребитель ОБЯЗАН передать сам (например, aria-label на кнопке-иконке)
|
|
109
|
+
— компонент не хардкодит текст, библиотека мультиязычная
|
|
110
|
+
-->
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Связанные компоненты
|
|
115
|
+
|
|
116
|
+
<!-- Компоненты, которые часто используются вместе или являются вариантами. -->
|
|
117
|
+
|
|
118
|
+
- `RelatedComponent` — описание связи
|
|
119
|
+
- `AnotherComponent` — описание связи
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Stories
|
|
124
|
+
|
|
125
|
+
Основные истории: `stories/Category/ComponentName.stories.tsx`
|
|
126
|
+
Файлы примеров: `stories/Category/examples/ComponentName/`
|
|
127
|
+
|
|
128
|
+
<!--
|
|
129
|
+
Колонка `Example file` — имя файла внутри `stories/Category/examples/ComponentName/`.
|
|
130
|
+
Именно этот файл бандлится в mcp-data.json как код примера, поэтому ссылка обязательна.
|
|
131
|
+
Если у story нет отдельного example-файла (редкий случай) — поставь `—`.
|
|
132
|
+
|
|
133
|
+
Стори `Playground` и `VisualTests` в bundle НЕ попадают (скрипт generateMcpData
|
|
134
|
+
фильтрует их по имени): Playground — интерактивная песочница без показа кода,
|
|
135
|
+
VisualTests — скриншот-регрессия. Но в таблице их всё равно оставляй —
|
|
136
|
+
таблица описывает все stories компонента для человека, а не только то,
|
|
137
|
+
что уходит в MCP.
|
|
138
|
+
-->
|
|
139
|
+
|
|
140
|
+
| Story | Example file | Что демонстрирует |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль всех props |
|
|
143
|
+
| `Themes` | `ThemesExample.tsx` | Все визуальные темы |
|
|
144
|
+
| `Sizes` | `SizesExample.tsx` | Размеры SM / MD / LG |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## История изменений
|
|
149
|
+
|
|
150
|
+
<!-- Краткий лог значимых изменений компонента. Агент дописывает при каждом изменении. -->
|
|
151
|
+
|
|
152
|
+
| Дата | Изменение |
|
|
153
|
+
|---|---|
|
|
154
|
+
| YYYY-MM-DD | Создан документ |
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Тестирование — Triplex-Next
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Обзор: три уровня тестирования
|
|
6
|
+
|
|
7
|
+
| Уровень | Инструмент | Расположение | Когда запускать |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| Unit | Vitest + Testing Library | `src/components/**/__tests__/` | `npm run test-unit` |
|
|
10
|
+
| Visual regression | Storybook test-runner + jest-image-snapshot | `__screenshots__/` | CI (PR) |
|
|
11
|
+
| E2E | Playwright | `e2e/tests/` | `npm run test-e2e` |
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Unit-тесты (Vitest + Testing Library)
|
|
16
|
+
|
|
17
|
+
### Когда писать
|
|
18
|
+
|
|
19
|
+
- **При изменении компонента:** добавить тест-кейс на новый функционал.
|
|
20
|
+
- **При создании нового компонента:** полное покрытие: все props, состояния, события, forwardRef.
|
|
21
|
+
- Баги исправляй с тестом, который воспроизводит баг.
|
|
22
|
+
|
|
23
|
+
### Расположение
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
src/components/{ComponentName}/__tests__/{ComponentName}.test.tsx
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Предпочтительный путь для новых тестов — `__tests__/`.
|
|
30
|
+
|
|
31
|
+
В текущем репозитории уже встречаются и другие локальные паттерны:
|
|
32
|
+
- `__tests__/`
|
|
33
|
+
- `__test__/`
|
|
34
|
+
- `tests/`
|
|
35
|
+
|
|
36
|
+
Если у компонента уже используется локальный паттерн, следуй ему и не делай
|
|
37
|
+
отдельную миграцию структуры без задачи.
|
|
38
|
+
|
|
39
|
+
Один файл на компонент. Если компонент имеет субкомпоненты (ButtonIcon, ButtonDropdown), тесты субкомпонентов — в отдельных файлах той же локальной папки тестов.
|
|
40
|
+
|
|
41
|
+
### Импорты
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import React from "react";
|
|
45
|
+
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
|
|
46
|
+
import userEvent from "@testing-library/user-event";
|
|
47
|
+
import { describe, it, expect, vi } from "vitest";
|
|
48
|
+
import { ComponentName } from "../ComponentName";
|
|
49
|
+
// Следуй локальному паттерну файла: для unit-тестов обычно используются локальные импорты
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**`userEvent` vs `fireEvent`:**
|
|
53
|
+
- Предпочитай `userEvent` — он точнее симулирует реальные события браузера (pointer events, keyboard, focus/blur цепочки).
|
|
54
|
+
- `fireEvent` допустим для простых случаев (триггер события без цепочки браузерных эффектов) или когда следуешь паттерну существующего файла теста.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// ✅ Предпочтительно для пользовательского взаимодействия
|
|
58
|
+
const user = userEvent.setup();
|
|
59
|
+
await user.click(screen.getByRole("button"));
|
|
60
|
+
await user.type(screen.getByRole("textbox"), "hello");
|
|
61
|
+
await user.keyboard("{Enter}");
|
|
62
|
+
|
|
63
|
+
// ✅ Допустимо для простых низкоуровневых событий
|
|
64
|
+
fireEvent.change(input, { target: { value: "hello" } });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Правило:
|
|
68
|
+
- В unit-тестах предпочитай локальные импорты для компонента под тестом и ближайших внутренних типов/утилит.
|
|
69
|
+
- В репозитории есть и package-style тесты. Не делай отдельную миграцию import-style без задачи на cleanup.
|
|
70
|
+
- Если тест проверяет именно публичный entrypoint библиотеки, импорт через `@sberbusiness/triplex-next` допустим.
|
|
71
|
+
|
|
72
|
+
### Структура
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
describe("ComponentName", () => {
|
|
76
|
+
// Хелпер для повторяющегося рендера
|
|
77
|
+
const renderComponent = (props = {}) =>
|
|
78
|
+
render(<ComponentName theme={ETheme.DEFAULT} {...props} />);
|
|
79
|
+
|
|
80
|
+
it("renders without errors", () => {
|
|
81
|
+
renderComponent();
|
|
82
|
+
expect(screen.getByRole("button")).toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("applies correct class for size prop", () => {
|
|
86
|
+
renderComponent({ size: EComponentSize.SM });
|
|
87
|
+
expect(screen.getByTestId("component-name")).toHaveClass("sm");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("calls onChange on click", () => {
|
|
91
|
+
const onChange = vi.fn();
|
|
92
|
+
renderComponent({ onChange });
|
|
93
|
+
fireEvent.click(screen.getByRole("button"));
|
|
94
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("forwards ref to root element", () => {
|
|
98
|
+
const ref = React.createRef<HTMLButtonElement>();
|
|
99
|
+
render(<ComponentName theme={ETheme.DEFAULT} ref={ref} />);
|
|
100
|
+
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("is disabled when disabled prop is set", () => {
|
|
104
|
+
renderComponent({ disabled: true });
|
|
105
|
+
expect(screen.getByRole("button")).toBeDisabled();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Приоритет запросов (Testing Library)
|
|
111
|
+
|
|
112
|
+
Используй запросы в этом порядке:
|
|
113
|
+
1. `getByRole` — семантические запросы (кнопки, инпуты, чекбоксы)
|
|
114
|
+
2. `getByLabelText` — для полей с подписями
|
|
115
|
+
3. `getByText` — для текстового содержимого
|
|
116
|
+
4. `getByTestId` — только если нет семантической альтернативы
|
|
117
|
+
|
|
118
|
+
**Async-варианты:** используй `findBy*` или `waitFor` для элементов, которые появляются после асинхронной операции:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// ✅ findBy* — сокращение для waitFor + getBy*
|
|
122
|
+
const successMessage = await screen.findByRole("alert");
|
|
123
|
+
|
|
124
|
+
// ✅ waitFor — для проверки условия, которое станет истинным со временем
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(screen.getByRole("button")).toBeEnabled();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ❌ не используй фиксированные таймауты
|
|
130
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// ✅ Предпочтительно
|
|
135
|
+
screen.getByRole("button", { name: "Submit" })
|
|
136
|
+
screen.getByRole("checkbox")
|
|
137
|
+
screen.getByLabelText("Email")
|
|
138
|
+
|
|
139
|
+
// ✅ Когда нет семантики
|
|
140
|
+
screen.getByTestId("loader-spinner")
|
|
141
|
+
|
|
142
|
+
// ❌ Избегать
|
|
143
|
+
container.querySelector(".button--loading") // тестирует реализацию
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Что тестировать
|
|
147
|
+
|
|
148
|
+
**Обязательно для каждого компонента:**
|
|
149
|
+
- [ ] Рендерится без ошибок с обязательными пропами
|
|
150
|
+
- [ ] `forwardRef` — ref передаётся в правильный DOM-элемент
|
|
151
|
+
- [ ] Все значения enum-props применяют корректный CSS-класс или атрибут
|
|
152
|
+
- [ ] `disabled` — компонент отключён и не вызывает обработчики
|
|
153
|
+
- [ ] Обработчики событий (onChange, onClick) вызываются корректно
|
|
154
|
+
- [ ] `className` пробрасывается в корневой элемент
|
|
155
|
+
|
|
156
|
+
**Для новых props (при изменении компонента):**
|
|
157
|
+
- [ ] Новый prop применяет ожидаемый класс / атрибут / поведение
|
|
158
|
+
- [ ] Пограничные значения (undefined, null, крайние значения enum)
|
|
159
|
+
|
|
160
|
+
**Не нужно тестировать:**
|
|
161
|
+
- Визуальное отображение (это задача visual regression тестов)
|
|
162
|
+
- Детали реализации LESS-классов, которые могут меняться
|
|
163
|
+
- Внутреннее состояние компонента (тестируй поведение, не state)
|
|
164
|
+
|
|
165
|
+
**Допустимо, когда это часть публичного состояния:**
|
|
166
|
+
- Проверять CSS-класс, если именно он кодирует публичный вариант или состояние (`size`, `theme`, `loading`, `expanded`)
|
|
167
|
+
- Проверять `aria-*`, `disabled`, `tabIndex`, текст и callback-контракт как часть публичного поведения
|
|
168
|
+
|
|
169
|
+
### Минимальная проверка после изменений
|
|
170
|
+
|
|
171
|
+
- Правки только документации/конфигов тестов: проверь актуальность команд, путей и примеров, отдельный прогон тестов не обязателен.
|
|
172
|
+
- Правки компонента с изменением публичного поведения: предпочитай focused unit test на затронутый компонент.
|
|
173
|
+
- Правки багфикса: сначала добавь или обнови тест, который воспроизводит сценарий.
|
|
174
|
+
- Правки accessibility, focus management, keyboard navigation, dropdown/dialog behavior: тест обязателен, если поведение нетривиальное или уже ломалось раньше.
|
|
175
|
+
- Если локально не запускал релевантный тест, явно укажи это в результате.
|
|
176
|
+
|
|
177
|
+
### Полезные матчеры (jest-dom)
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
expect(el).toBeInTheDocument()
|
|
181
|
+
expect(el).toBeVisible()
|
|
182
|
+
expect(el).toBeDisabled()
|
|
183
|
+
expect(el).toBeChecked()
|
|
184
|
+
expect(el).toHaveClass("sm")
|
|
185
|
+
expect(el).toHaveAttribute("aria-expanded", "true")
|
|
186
|
+
expect(el).toHaveTextContent("Submit")
|
|
187
|
+
expect(el).toHaveFocus()
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Мокирование
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// Мок функции
|
|
194
|
+
const onChangeMock = vi.fn();
|
|
195
|
+
|
|
196
|
+
// Мок модуля
|
|
197
|
+
vi.mock("../utils", () => ({
|
|
198
|
+
formatDate: vi.fn(() => "01.01.2024"),
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
// Очистка после теста (если нужно)
|
|
202
|
+
afterEach(() => {
|
|
203
|
+
vi.clearAllMocks();
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Visual regression тесты (Storybook test-runner)
|
|
210
|
+
|
|
211
|
+
### Как работает
|
|
212
|
+
|
|
213
|
+
- **Каждая story** автоматически снимается на двух viewport'ах: `xs` (575px) и `xl` (1200px).
|
|
214
|
+
- Скриншоты сравниваются с baseline в `__screenshots__/`.
|
|
215
|
+
- Baseline **только из Docker/CI** (Linux). Никогда не коммить скриншоты с локальной macOS.
|
|
216
|
+
|
|
217
|
+
### Когда нужны Visual tests
|
|
218
|
+
|
|
219
|
+
Создавай отдельную `VisualTests` story если:
|
|
220
|
+
- Компонент требует взаимодействия перед скриншотом (открыть dropdown, hover, focus).
|
|
221
|
+
- Нужно покрыть состояние, которого нет в документационных stories.
|
|
222
|
+
- Нужно объединить несколько вариантов в один скриншот.
|
|
223
|
+
|
|
224
|
+
### Обновление baseline
|
|
225
|
+
|
|
226
|
+
```text
|
|
227
|
+
GitHub → Actions → "Update Visual Snapshots" → Run workflow → выбери ветку
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Workflow сам закоммитит обновлённые скриншоты в ветку.
|
|
231
|
+
|
|
232
|
+
### Локальная проверка (опционально)
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm run test-visual:docker # прогон через Docker
|
|
236
|
+
npm run test-visual:docker:update # обновление baseline через Docker
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Что делать при упавших visual тестах
|
|
240
|
+
|
|
241
|
+
1. Проверить, ожидаемо ли изменение (намеренное изменение дизайна?).
|
|
242
|
+
2. Если да — запустить `Update Visual Snapshots` workflow.
|
|
243
|
+
3. Если нет — найти и исправить визуальную регрессию в коде.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## E2E тесты (Playwright)
|
|
248
|
+
|
|
249
|
+
### Когда писать
|
|
250
|
+
|
|
251
|
+
- Критичные пользовательские флоу, затрагивающие несколько компонентов.
|
|
252
|
+
- Взаимодействия, которые нельзя протестировать в unit-тестах (например, drag-and-drop, clipboard).
|
|
253
|
+
- Не нужен для каждого компонента — только для сложных флоу.
|
|
254
|
+
|
|
255
|
+
### Расположение
|
|
256
|
+
|
|
257
|
+
```text
|
|
258
|
+
e2e/tests/{componentName}.spec.ts
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Структура
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { test, expect, Page } from "@playwright/test";
|
|
265
|
+
|
|
266
|
+
test.describe("ComponentName", () => {
|
|
267
|
+
test("should do something on user action", async ({ page }) => {
|
|
268
|
+
await page.goto("http://localhost:6006/iframe.html?id=category--story-name");
|
|
269
|
+
|
|
270
|
+
const element = page.getByRole("button", { name: "Submit" });
|
|
271
|
+
await expect(element).toBeVisible();
|
|
272
|
+
await element.click();
|
|
273
|
+
|
|
274
|
+
await expect(page.getByText("Success")).toBeVisible();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Правила
|
|
280
|
+
|
|
281
|
+
- **Не используй фиксированные таймауты** (`page.waitForTimeout`) как основной способ синхронизации. Используй `waitFor`, `toBeVisible`, `toBeEnabled`.
|
|
282
|
+
- Исключение: короткий `waitForTimeout(100-300ms)` для ожидания CSS-анимации допустим, если иначе состояние надёжно не поймать. Такой таймаут должен быть коротким и обоснованным.
|
|
283
|
+
- Тесты должны быть **независимы** друг от друга (не полагаться на порядок выполнения).
|
|
284
|
+
- URL для теста — iframe Storybook конкретной story.
|
|
285
|
+
- Не переименовывай story export/name без синхронного обновления e2e-URL и visual baseline.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Запуск тестов
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# Unit
|
|
293
|
+
npm run test-unit # одноразовый прогон
|
|
294
|
+
npm run test-unit:watch # watch-режим
|
|
295
|
+
|
|
296
|
+
# Visual (требует запущенного Storybook на :6006)
|
|
297
|
+
npm run test-visual:docker
|
|
298
|
+
|
|
299
|
+
# E2E (требует запущенного Storybook)
|
|
300
|
+
npm run test-e2e
|
|
301
|
+
```
|