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:
2026-08-21 09:45:05 +05:00
parent 05cd491769
commit 3cf46fa55c
8 changed files with 626 additions and 10 deletions
+49
View File
@@ -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);
});
});
});