test: добавляет Vitest и CI-workflow по образцу request-portal
Покрыты чистые функции бизнес-логики: validateFeedback (обязательность полей по видам обращения, минимальная длина текста, анонимность) и getAppealFieldsConfig (согласованность конфига — обязательное поле не может быть скрыто). React-компоненты пока не тестируются: сначала нужно решить, куда форма отправляет данные, иначе тесты придётся сразу переписывать. vite.config.ts: defineConfig теперь из 'vitest/config' — один файл конфига на сборку и тесты вместо двух рассинхронизирующихся. globals: true и environment: 'node', т.к. тестируется логика, а не DOM. .gitea/workflows/test.yml — тот же принцип, что в request-portal (push в main, PR, ретраи npm install), но npm run ci вместо heft test: здесь нет SPFx/Jest, и деплой пока не добавлен — куда разворачивать dist/, не решено. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { COMPANY_OPTIONS, getAppealFieldsConfig } from './model';
|
||||
|
||||
// Форма (AppealSection) и валидация (validateFeedback) читают одно и то же
|
||||
// правило из getAppealFieldsConfig — если оно разойдётся, форма и валидация
|
||||
// начнут спорить друг с другом. Эти тесты фиксируют контракт правила.
|
||||
describe('getAppealFieldsConfig', () => {
|
||||
it('жалоба: описание обязательно, предложение показано, но необязательно', () => {
|
||||
const fields = getAppealFieldsConfig('complaint');
|
||||
expect(fields.situation).toEqual({ visible: true, required: true });
|
||||
expect(fields.suggestion).toEqual({ visible: true, required: false });
|
||||
});
|
||||
|
||||
it('предложение: описания ситуации нет вовсе, само предложение обязательно', () => {
|
||||
const fields = getAppealFieldsConfig('suggestion');
|
||||
expect(fields.situation).toEqual({ visible: false, required: false });
|
||||
expect(fields.suggestion).toEqual({ visible: true, required: true });
|
||||
});
|
||||
|
||||
it('другое: обязательно только описание, предложения не показываем', () => {
|
||||
const fields = getAppealFieldsConfig('other');
|
||||
expect(fields.situation).toEqual({ visible: true, required: true });
|
||||
expect(fields.suggestion).toEqual({ visible: false, required: false });
|
||||
});
|
||||
|
||||
it('обязательное поле не может быть скрыто ни для одного вида обращения', () => {
|
||||
// Форма не сможет попросить исправить поле, которого не видно на экране —
|
||||
// required: true при visible: false — это несогласованная настройка.
|
||||
(['complaint', 'suggestion', 'other'] as const).forEach((kind) => {
|
||||
const fields = getAppealFieldsConfig(kind);
|
||||
expect(fields.situation.required && !fields.situation.visible).toBe(false);
|
||||
expect(fields.suggestion.required && !fields.suggestion.visible).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Справочник компаний используется бэкендом по Id (см. комментарий в model.ts) —
|
||||
// дубликат Id незаметно перепутал бы два разных юрлица.
|
||||
describe('COMPANY_OPTIONS', () => {
|
||||
it('не содержит повторяющихся Id', () => {
|
||||
const ids = COMPANY_OPTIONS.map((option) => option.value);
|
||||
expect(new Set(ids).size).toBe(ids.length);
|
||||
});
|
||||
|
||||
it('у каждой компании есть непустое название', () => {
|
||||
COMPANY_OPTIONS.forEach((option) => {
|
||||
expect(option.label.trim().length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
import { EMPTY_FEEDBACK, type IFeedbackData } from './model';
|
||||
import { TEXTS } from './texts';
|
||||
import { validateFeedback } from './validateFeedback';
|
||||
|
||||
const VALID_COMPANY = '9de064a4-44c8-88ef-11ec-8285925d83d2';
|
||||
const LONG_TEXT = 'Развёрнутое описание, которого достаточно для проверки правила';
|
||||
const SHORT_TEXT = 'коротко';
|
||||
|
||||
/** Полностью заполненное «жалоба»-обращение — стартовая точка для тестов. */
|
||||
function buildValidComplaint(overrides: Partial<IFeedbackData> = {}): IFeedbackData {
|
||||
return {
|
||||
...EMPTY_FEEDBACK,
|
||||
company: VALID_COMPANY,
|
||||
fullName: 'Иванов Иван Иванович',
|
||||
kind: 'complaint',
|
||||
situation: LONG_TEXT,
|
||||
suggestion: LONG_TEXT,
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
describe('validateFeedback: базовые обязательные поля', () => {
|
||||
it('полностью заполненное обращение проходит без ошибок', () => {
|
||||
expect(validateFeedback(buildValidComplaint())).toEqual({});
|
||||
});
|
||||
|
||||
it('без компании — ошибка', () => {
|
||||
const errors = validateFeedback(buildValidComplaint({ company: '' }));
|
||||
expect(errors.company).toBeDefined();
|
||||
});
|
||||
|
||||
it('именное обращение без ФИО — ошибка', () => {
|
||||
const errors = validateFeedback(buildValidComplaint({ fullName: '' }));
|
||||
expect(errors.fullName).toBeDefined();
|
||||
});
|
||||
|
||||
it('анонимное обращение без ФИО — не ошибка (поле скрыто, требовать его нельзя)', () => {
|
||||
const errors = validateFeedback(
|
||||
buildValidComplaint({ isAnonymous: true, fullName: '' })
|
||||
);
|
||||
expect(errors.fullName).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateFeedback: вид обращения "Другое"', () => {
|
||||
it('без уточнения вида — ошибка', () => {
|
||||
const errors = validateFeedback(
|
||||
buildValidComplaint({ kind: 'other', kindOther: '', situation: LONG_TEXT })
|
||||
);
|
||||
expect(errors.kindOther).toBeDefined();
|
||||
});
|
||||
|
||||
it('с уточнением — ошибки по kindOther нет', () => {
|
||||
const errors = validateFeedback(
|
||||
buildValidComplaint({ kind: 'other', kindOther: 'Свой вариант', situation: LONG_TEXT })
|
||||
);
|
||||
expect(errors.kindOther).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// Состав обязательных полей зависит от вида обращения (см. getAppealFieldsConfig
|
||||
// в model.ts) — эти тесты фиксируют контракт, чтобы форма и валидация не разошлись.
|
||||
describe('validateFeedback: обязательность полей по видам обращения', () => {
|
||||
it('жалоба: описание ситуации обязательно', () => {
|
||||
const errors = validateFeedback(buildValidComplaint({ situation: '' }));
|
||||
expect(errors.situation).toBeDefined();
|
||||
});
|
||||
|
||||
it('жалоба: предложение по улучшению НЕ обязательно', () => {
|
||||
const errors = validateFeedback(buildValidComplaint({ suggestion: '' }));
|
||||
expect(errors.suggestion).toBeUndefined();
|
||||
});
|
||||
|
||||
it('жалоба: если предложение всё же указано, но слишком коротко — ошибка', () => {
|
||||
const errors = validateFeedback(buildValidComplaint({ suggestion: SHORT_TEXT }));
|
||||
expect(errors.suggestion).toBeDefined();
|
||||
});
|
||||
|
||||
it('предложение: описание ситуации не требуется, даже если пустое', () => {
|
||||
const errors = validateFeedback(
|
||||
buildValidComplaint({ kind: 'suggestion', situation: '', suggestion: LONG_TEXT })
|
||||
);
|
||||
expect(errors.situation).toBeUndefined();
|
||||
});
|
||||
|
||||
it('предложение: сама суть предложения обязательна', () => {
|
||||
const errors = validateFeedback(
|
||||
buildValidComplaint({ kind: 'suggestion', situation: '', suggestion: '' })
|
||||
);
|
||||
expect(errors.suggestion).toBeDefined();
|
||||
});
|
||||
|
||||
it('другое: описание обязательно, предложение не спрашивается вовсе', () => {
|
||||
const errors = validateFeedback(
|
||||
buildValidComplaint({
|
||||
kind: 'other',
|
||||
kindOther: 'Свой вариант',
|
||||
situation: '',
|
||||
suggestion: ''
|
||||
})
|
||||
);
|
||||
expect(errors.situation).toBeDefined();
|
||||
expect(errors.suggestion).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateFeedback: минимальная длина текста', () => {
|
||||
it('пустое описание — ошибка «заполните поле»', () => {
|
||||
const errors = validateFeedback(buildValidComplaint({ situation: '' }));
|
||||
expect(errors.situation).toBe(TEXTS.errors.required);
|
||||
});
|
||||
|
||||
it('слишком короткое описание — ошибка «коротко», а не «пусто»', () => {
|
||||
const errors = validateFeedback(buildValidComplaint({ situation: SHORT_TEXT }));
|
||||
expect(errors.situation).toBe(TEXTS.errors.tooShort);
|
||||
});
|
||||
|
||||
it('достаточно длинное описание — без ошибки', () => {
|
||||
const errors = validateFeedback(buildValidComplaint({ situation: LONG_TEXT }));
|
||||
expect(errors.situation).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user