Правка arm-web 9e89b9f перенесена в arm-core/web: id и origin у строк и ячеек формы, ключ достроенной строки code-<код>, отправка line_code, раскладка formLayout. Без неё встроенный фронт отправлял бы line_id "null" (I-1, R-12). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
110 lines
4.5 KiB
TypeScript
110 lines
4.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import type { StatementOut, ValueOut } from '../../types/api';
|
||
import { applyDraftInput, buildStatementGrid, cellFlags, cellKey, draftToPatch } from './statementGrid';
|
||
|
||
const v = (id: string | null, column_id: string, column_index: number, extra: Partial<ValueOut> = {}): ValueOut => ({
|
||
id,
|
||
column_id,
|
||
column_index,
|
||
amount: '1.00',
|
||
string_value: null,
|
||
confidence: '100',
|
||
is_recognized: true,
|
||
is_modified: false,
|
||
original_amount: null,
|
||
...extra,
|
||
});
|
||
|
||
const statement = {
|
||
columns: [
|
||
{ id: 'c2', column_index: 1, label: '31.12.2023', period_date: null, is_current: false },
|
||
{ id: 'c1', column_index: 0, label: '30.09.2024', period_date: null, is_current: true },
|
||
],
|
||
lines: [{ id: 'l1', origin: 'source', line_code: '1600', line_name: 'Баланс', values: [v('v2', 'c2', 1), v('v1', 'c1', 0)] }],
|
||
} as unknown as StatementOut;
|
||
|
||
describe('buildStatementGrid', () => {
|
||
it('колонки по column_index, ячейки по column_id', () => {
|
||
const grid = buildStatementGrid(statement);
|
||
expect(grid.columns.map((c) => c.id)).toEqual(['c1', 'c2']);
|
||
expect(grid.rows[0]).toMatchObject({ key: 'l1', lineId: 'l1', code: '1600', name: 'Баланс', origin: 'source' });
|
||
expect(grid.rows[0].cells.c1?.id).toBe('v1');
|
||
expect(grid.rows[0].cells.c2?.id).toBe('v2');
|
||
});
|
||
it('достроенная строка: ключ по коду, lineId = null', () => {
|
||
const withTemplate = {
|
||
...statement,
|
||
lines: [
|
||
...statement.lines,
|
||
{
|
||
id: null,
|
||
origin: 'template',
|
||
line_code: '1700',
|
||
line_name: 'БАЛАНС',
|
||
values: [v(null, 'c1', 0, { amount: null, confidence: null, is_recognized: null })],
|
||
},
|
||
],
|
||
} as unknown as StatementOut;
|
||
const row = buildStatementGrid(withTemplate).rows[1];
|
||
expect(row).toMatchObject({ key: 'code-1700', lineId: null, code: '1700', origin: 'template' });
|
||
});
|
||
});
|
||
|
||
describe('cellFlags', () => {
|
||
it('внимание: уверенность < 100 или не распознано', () => {
|
||
expect(cellFlags(v('a', 'c', 0, { confidence: '87.5' })).attention).toBe(true);
|
||
expect(cellFlags(v('a', 'c', 0, { is_recognized: false })).attention).toBe(true);
|
||
expect(cellFlags(v('a', 'c', 0)).attention).toBe(false);
|
||
expect(cellFlags(v('a', 'c', 0, { confidence: null })).attention).toBe(false);
|
||
});
|
||
it('пустая достроенная ячейка не требует внимания', () => {
|
||
expect(cellFlags(v(null, 'c', 0, { amount: null, confidence: null, is_recognized: null }))).toEqual({
|
||
attention: false,
|
||
modified: false,
|
||
});
|
||
});
|
||
it('исправлено', () => {
|
||
expect(cellFlags(v('a', 'c', 0, { is_modified: true })).modified).toBe(true);
|
||
expect(cellFlags(undefined)).toEqual({ attention: false, modified: false });
|
||
});
|
||
});
|
||
|
||
describe('draftToPatch', () => {
|
||
const rows = buildStatementGrid({
|
||
...statement,
|
||
lines: [...statement.lines, { id: null, origin: 'template', line_code: '1700', line_name: 'БАЛАНС', values: [] }],
|
||
} as unknown as StatementOut).rows;
|
||
it('строка из БД — line_id, достроенная — line_code', () => {
|
||
expect(draftToPatch({ [cellKey('l1', 'c1')]: '10.5', [cellKey('code-1700', 'c1')]: null }, rows)).toEqual([
|
||
{ line_id: 'l1', column_id: 'c1', amount: '10.5' },
|
||
{ line_code: '1700', column_id: 'c1', amount: null },
|
||
]);
|
||
});
|
||
it('неизвестная строка — ошибка, а не тихий пропуск', () => {
|
||
expect(() => draftToPatch({ [cellKey('ghost', 'c1')]: '1' }, rows)).toThrow();
|
||
});
|
||
});
|
||
|
||
describe('applyDraftInput', () => {
|
||
const parse = (s: string): { ok: true; value: string | null } | { ok: false } => {
|
||
const n = s.replace(/\s/g, '').replace(',', '.');
|
||
if (n === '') {
|
||
return { ok: true, value: null };
|
||
}
|
||
return /^-?\d+(\.\d+)?$/.test(n) ? { ok: true, value: n } : { ok: false };
|
||
};
|
||
it('изменение попадает в черновик', () => {
|
||
expect(applyDraftInput({}, 'k', '5', '1.00', parse)).toEqual({ k: '5' });
|
||
});
|
||
it('возврат к исходному убирает ключ (с учётом нулей дроби)', () => {
|
||
expect(applyDraftInput({ k: '5' }, 'k', '1', '1.00', parse)).toEqual({});
|
||
expect(applyDraftInput({ k: '5' }, 'k', '1,0', '1.00', parse)).toEqual({});
|
||
});
|
||
it('пустое при пустом исходном — не изменение', () => {
|
||
expect(applyDraftInput({ k: '5' }, 'k', '', null, parse)).toEqual({});
|
||
});
|
||
it('неверный формат остаётся в черновике', () => {
|
||
expect(applyDraftInput({}, 'k', '12,3,4', '1.00', parse)).toEqual({ k: '12,3,4' });
|
||
});
|
||
});
|