| name | api-test |
| description | Create an API type-check test for a component. Use when asked to create, generate, or write an API test for a component. Example - "создай api-test для Checkbox", "api-test Switch". |
| argument-hint | [ComponentName] |
| allowed-tools | Glob, Grep, Read, Write, Edit, Bash, Agent |
Создание API type-check теста для компонента $ARGUMENTS
Тесты проверяют публичный API (типы пропсов) компонента через expectTypeOf из expect-type.
Исходный файл пишется для @salutejs/plasma-b2c, затем script.mjs автоматически генерирует варианты для всех библиотек.
Архитектурный контекст
- Тесты:
utils/api-tests/src/components/{ComponentName}/{ComponentName}.api.test.tsx
utils/api-tests/script.mjs копирует тесты из src/ в tests/{lib}/, заменяя импорт @salutejs/plasma-b2c на каждую целевую библиотеку
Шаг 1 — Найти определение типов компонента
- Найти экспорт компонента в
packages/plasma-b2c
- Найти основные типы в
packages/plasma-new-hope/src/components/$ARGUMENTS/*.types.ts либо во внутренних папках компонента.
- Обратить внимание на:
- Дискриминированные юнионы (conditional props)
- Пропсы из
Pick<> от новых типов
- Наследование от HTML-элементов (
HTMLInputElement, HTMLButtonElement, HTMLDivElement)
- Дженерики (если есть)
Шаг 2 — Создать файл теста
Путь: utils/api-tests/src/components/$ARGUMENTS/$ARGUMENTS.api.test.tsx
Структура файла
import * as React from 'react';
import type { ComponentProps, ReactNode, CSSProperties, AriaRole } from 'react';
import { describe, it } from 'node:test';
import { expectTypeOf } from 'expect-type';
import { $ARGUMENTS } from '@salutejs/plasma-b2c';
type ${ARGUMENTS}Props = ComponentProps<typeof $ARGUMENTS>;
describe('Basics', () => {
it('Common', () => {
});
it('Variations', () => {
});
it('HTML...Element', () => {
});
});
describe('Unions', () => {
it('UnionName', () => {
});
});
describe('Generics', () => {
it('ItemOption', () => {
});
});
describe('Examples', () => {
it('Basic', () => {
() => {
return (<$ARGUMENTS />);
};
});
});
Правила написания тестов
Секция Common
Каждый собственный проп проверяется отдельной строкой. Группировка: layout → state → content slots → callbacks.
expectTypeOf<Props>().toHaveProperty('disabled').toEqualTypeOf<boolean | undefined>();
expectTypeOf<Props>().toHaveProperty('label').toEqualTypeOf<string | undefined>();
expectTypeOf<Props>()
.toHaveProperty('pin')
.toEqualTypeOf<'square-square' | 'square-clear' | 'clear-square' | | undefined>();
expectTypeOf<Props>().toHaveProperty('children').toEqualTypeOf<ReactNode>();
expectTypeOf<Props>().toHaveProperty('contentLeft').toEqualTypeOf<ReactNode>();
expectTypeOf<Props>()
.toHaveProperty('onChange')
.toEqualTypeOf<React.ChangeEventHandler<HTMLInputElement> | undefined>();
Важно: optional пропсы (prop?: Type) всегда включают | undefined в ожидаемом типе.
Секция Variations
Вариативные пропсы (значения определяются конфигом и отличаются между библиотеками) проверяются паттерном "подмножество string, но не сам string":
type View = NonNullable<Props['view']>;
expectTypeOf<View>().toExtend<string>();
expectTypeOf<string>().not.toExtend<View>();
Типичные вариации: view, size, labelPlacement, chipView, hintView, hintSize.
Внимание: не все пропсы с типом string являются вариациями. Если конфиг компонента не сужает проп до конкретных литералов, NonNullable даст string и expectTypeOf<string>().not.toExtend<string>() сломается. Перед добавлением в Variations — проверить, что конфиг действительно определяет конкретные значения для этого пропа.
Секция HTML Element
Название it по реальному элементу: 'HTMLInputElement', 'HTMLButtonElement' и т.д.
Общие для всех:
expectTypeOf<Props>().toHaveProperty('id').toEqualTypeOf<string | undefined>();
expectTypeOf<Props>().toHaveProperty('className').toEqualTypeOf<string | undefined>();
expectTypeOf<Props>().toHaveProperty('style').toEqualTypeOf<CSSProperties | undefined>();
expectTypeOf<Props>().toHaveProperty('aria-label').toEqualTypeOf<string | undefined>();
expectTypeOf<Props>().toHaveProperty('role').toEqualTypeOf<AriaRole | undefined>();
Для HTMLInputElement — добавить:
expectTypeOf<Props>().toHaveProperty('value').toEqualTypeOf<string | number | readonly string[] | undefined>();
expectTypeOf<Props>().toHaveProperty('defaultValue').toEqualTypeOf<string | number | readonly string[] | undefined>();
expectTypeOf<Props>()
.toHaveProperty('onChange')
.toEqualTypeOf<React.ChangeEventHandler<HTMLInputElement> | undefined>();
expectTypeOf<Props>().toHaveProperty('onFocus').toEqualTypeOf<React.FocusEventHandler<HTMLInputElement> | undefined>();
expectTypeOf<Props>().toHaveProperty('onBlur').toEqualTypeOf<React.FocusEventHandler<HTMLInputElement> | undefined>();
expectTypeOf<Props>()
.toHaveProperty('onKeyDown')
.toEqualTypeOf<React.KeyboardEventHandler<HTMLInputElement> | undefined>();
Для HTMLButtonElement / HTMLElement — добавить:
expectTypeOf<Props>().toHaveProperty('onClick').toEqualTypeOf<React.MouseEventHandler<HTMLElement> | undefined>();
expectTypeOf<Props>().toHaveProperty('onMouseEnter').toEqualTypeOf<React.MouseEventHandler<HTMLElement> | undefined>();
expectTypeOf<Props>().toHaveProperty('onMouseLeave').toEqualTypeOf<React.MouseEventHandler<HTMLElement> | undefined>();
Секция Unions
Дискриминированные юнионы проверяются через expectTypeOf<Props>({...}):
it('UnionName', () => {
expectTypeOf<Props>({ propA: 'value1', propB: true });
expectTypeOf<Props>({ propA: 'value2' });
expectTypeOf<Props>({ propA: 'value1', forbiddenProp: true });
expectTypeOf<Props>({ propA: 'value1', shouldBeForbidden: true });
});
Секция Generics
Для компонентов с дженериками (например, items с произвольными полями) — через JSX:
it('ItemType', () => {
const items = [{ value: '', label: '', customProp: '', boolProp: true }];
void (<Component items={items} />);
void (
<Component
items={items}
renderItem={(item) => item.customProp}
filter={(item) => item.boolProp}
onChange={(value, item) => item && item.customProp}
/>
);
});
Секция Complex / Examples
Реалистичные примеры через expectTypeOf — комбинации пропсов, отражающие реальное использование:
it('Examples', () => {
expectTypeOf<Props>({ label: 'Текст', disabled: true });
expectTypeOf<Props>({ text: 'Кнопка', stretching: 'filled' });
});
Секция Examples (JSX)
Примеры с реальным JSX и хуками — обёрнуты в анонимную функцию:
describe('Examples', () => {
it('Controlled', () => {
() => {
const [value, setValue] = useState('');
return <Component value={value} onChange={(e) => setValue(e.target.value)} label="Метка" />;
};
});
it('Disabled', () => {
() => {
return <Component label="Неактивный" disabled />;
};
});
});
Шаг 3 — Запуск и валидация
Запустить полный цикл тестов (генерация + typecheck):
cd utils/api-tests && npm test
Это выполнит rm -rf tests && node script.mjs && NODE_OPTIONS=--max-old-space-size=8192 tsc --noEmit -p ./tsconfig.typecheck.json — сгенерирует тесты для всех библиотек и запустит typecheck.
Все тесты должны пройти без ошибок типов (Type Errors: no errors).
Рантайм ошибки Cannot find module 'styled-components' — ожидаемые, игнорировать.
Если есть ошибки типов — внимательно прочитать полный вывод (строки с TypeCheckError), исправить ассерты в src/ файле (не в компоненте) и перезапустить.
Важные правила
- Только
@salutejs/plasma-b2c в импортах — script.mjs заменит на остальные библиотеки
- Типы из
plasma-new-hope как источник истины — не тестировать b2c-only пропсы (например status, caption, animatedHint для TextField). Проп может быть b2c-only для одного компонента, но общим для другого (например helperText — b2c-only в TextField, но общий в Combobox). Всегда проверять по new-hope типам конкретного компонента
- Вариативные пропсы — паттерн
toExtend, НЕ toEqualTypeOf — конфиги сужают по-разному в каждой библиотеке
- Не проверять
@deprecated пропсы (помеченные @deprecated)
- Не проверять internal пропсы (помеченные
@internal или начинающиеся с $)
- JSX-примеры должны компилироваться — это реальные type-check проверки, не документация
- Группировать пропсы логически в Common: layout → state → content slots → callbacks
- Юнионы с обеих сторон — валидные комбинации И
@ts-expect-error для невалидных, или // TODO если юнион "дырявый"
Чеклист
Референсные файлы
Изучить для понимания паттернов:
utils/api-tests/src/components/Button/Button.api.test.tsx — простой компонент, один юнион, без дженериков
utils/api-tests/src/components/TextField/TextField.api.test.tsx — сложный компонент, множественные юнионы, chip-пропсы
utils/api-tests/src/components/Combobox/Combobox.api.test.tsx — дженерики, сложные юнионы, JSX-примеры