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 => ({ 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' }); }); });