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,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