import assert from 'node:assert/strict'; import test from 'node:test'; import { buildRowHierarchy, canAddRowForHeader, getDataRowId, getDeleteTarget, getForm4TypeLabel, getProjectAddTarget, getRenameTarget, isRenameResponse, } from './rowUtils.js'; test('row hierarchy uses row types when depth skips a level and keeps INPUT rows as siblings', () => { const rows = [ { row_type: 'ROOT', depth: 0, id: 'root' }, { row_type: 'ITEM', depth: 2, id: 'item-1' }, { row_type: 'INPUT', depth: 4, id: 'input-1' }, { row_type: 'INPUT', depth: 4, id: 'input-2' }, { row_type: 'ITEM', depth: 2, id: 'item-2' }, { row_type: 'ROOT', depth: 0, id: 'next-root' }, ]; const tree = buildRowHierarchy(rows); assert.deepEqual( tree.map(({ id }) => id), ['root', 'next-root'], ); assert.deepEqual( tree[0].subRows.map(({ id }) => id), ['item-1', 'item-2'], ); assert.deepEqual( tree[0].subRows[0].subRows.map(({ id }) => id), ['input-1', 'input-2'], ); assert.deepEqual(tree[0].subRows[0].subRows[0].subRows, []); }); // AURORA-1887: лист AHR блокирует «Добавить строку» для статей // аренды/коммуналки/охраны через header.can_add_row=false; для всех прочих // статей (включая лимитные, не трогаем AURORA-1907) и для листов, где это // поле вовсе не считается (не AHR), добавление остаётся разрешённым. test('canAddRowForHeader blocks only an explicit can_add_row=false', () => { assert.equal(canAddRowForHeader({ can_add_row: false }), false); assert.equal(canAddRowForHeader({ can_add_row: true }), true); assert.equal(canAddRowForHeader({ can_add_row: null }), true); assert.equal(canAddRowForHeader({}), true); assert.equal(canAddRowForHeader(undefined), true); }); test('AURORA-1848: getDataRowId без id и line_id возвращает null и сериализуется в row_id:null', () => { assert.equal(getDataRowId({ header: {} }), null); assert.equal(getDataRowId({ id: 5 }), 5); assert.equal(getDataRowId({ line_id: 7 }), 7); assert.ok(JSON.stringify({ row_id: getDataRowId({ header: {} }) }).includes('"row_id":null')); }); // AURORA-1978: «Удалить строку» на форме 4 для строки программы (GROUP) и // проекта (ITEM). У этих строк нет line_id — раньше кнопка молча ничего не // делала; теперь цель удаления — программа/проект по id из header. const form4Row = (row_type, header, subRows = []) => ({ row_type, data: { line_id: null, header }, subRows }); test('AURORA-1978: getDeleteTarget — пустая программа и пустой проект формы 4', () => { assert.deepEqual(getDeleteTarget(form4Row('GROUP', { program_id: 11, name: 'Программа А' }), { isForm4: true }), { kind: 'program', id: 11, name: 'Программа А', blockedReason: null, }); assert.deepEqual(getDeleteTarget(form4Row('ITEM', { program_id: 11, project_id: 21, name: 'Проект Б' }), { isForm4: true }), { kind: 'project', id: 21, name: 'Проект Б', blockedReason: null, }); }); test('AURORA-1978: getDeleteTarget — непустые программа и проект запрещены с причиной', () => { const project = form4Row('ITEM', { program_id: 11, project_id: 21, name: 'Проект Б' }, [ { row_type: 'INPUT', data: { line_id: 501, header: {} }, subRows: [] }, ]); const program = form4Row('GROUP', { program_id: 11, name: 'Программа А' }, [project]); assert.equal(getDeleteTarget(program, { isForm4: true }).blockedReason, 'Нельзя удалить программу: в ней есть проекты или расходы'); assert.equal(getDeleteTarget(project, { isForm4: true }).blockedReason, 'Нельзя удалить проект: в нём есть расходы'); }); test('AURORA-1978: getDeleteTarget — строка бюджета и прочие формы удаляются по line_id, как раньше', () => { const input = { row_type: 'INPUT', data: { line_id: 501, header: { program_id: 11, project_id: 21 } }, subRows: [] }; assert.deepEqual(getDeleteTarget(input, { isForm4: true }), { kind: 'row', id: 501 }); assert.deepEqual(getDeleteTarget({ row_type: 'INPUT', data: { id: 7 } }), { kind: 'row', id: 7 }); // GROUP вне формы 4 (иерархия статей) — удалять нечего. assert.equal(getDeleteTarget(form4Row('GROUP', { program_id: 11 })), null); assert.equal(getDeleteTarget(form4Row('ROOT', { section_code: 'S' }), { isForm4: true }), null); assert.equal(getDeleteTarget(null, { isForm4: true }), null); }); test('AURORA-1984: getProjectAddTarget — цель по выделенной строке', () => { assert.deepEqual(getProjectAddTarget(undefined), {}); assert.deepEqual(getProjectAddTarget(form4Row('ROOT', { section_code: 'CAP' })), {}); assert.deepEqual(getProjectAddTarget(form4Row('GROUP', { program_id: 11 })), { program_id: 11 }); assert.deepEqual(getProjectAddTarget(form4Row('ITEM', { program_id: 11, project_id: 21 })), { program_id: 11 }); assert.deepEqual(getProjectAddTarget(form4Row('ITEM', { program_id: null, project_id: 21 })), {}); assert.deepEqual(getProjectAddTarget(form4Row('ITEM', { project_id: 21 })), {}); assert.deepEqual(getProjectAddTarget(form4Row('INPUT', { program_id: 11, project_id: 21 })), { program_id: 11 }); }); test('AURORA-1980: getRenameTarget — программа, проект, остальное', () => { assert.deepEqual(getRenameTarget(form4Row('GROUP', { program_id: 11, name: 'А' })), { kind: 'program', id: 11, name: 'А' }); assert.deepEqual(getRenameTarget(form4Row('ITEM', { project_id: 21, name: 'Б' })), { kind: 'project', id: 21, name: 'Б' }); assert.equal(getRenameTarget(form4Row('ROOT', { section_code: 'CAP' })), null); assert.equal(getRenameTarget(form4Row('INPUT', { project_id: 21 })), null); assert.equal(getRenameTarget(undefined), null); }); test('AURORA-1980: isRenameResponse — только свой ответ на свой узел', () => { const target = { kind: 'project', id: 21, name: 'Б' }; const msg = { is_self: true, event: 'project_updated', data: { project_id: 21 } }; assert.equal(isRenameResponse(msg, target), true); assert.equal(isRenameResponse({ ...msg, error: 'нет прав' }, target), true); assert.equal(isRenameResponse({ ...msg, is_self: false }, target), false); assert.equal(isRenameResponse({ ...msg, data: { project_id: 22 } }, target), false); assert.equal(isRenameResponse({ ...msg, event: 'program_updated', data: { program_id: 21 } }, target), false); assert.equal(isRenameResponse(msg, null), false); }); test('AURORA-1979: getForm4TypeLabel', () => { assert.equal(getForm4TypeLabel(form4Row('GROUP', { program_id: 11 })), 'программа'); assert.equal(getForm4TypeLabel(form4Row('ITEM', { project_id: 21 })), 'проект'); assert.equal(getForm4TypeLabel(form4Row('ITEM', { program_id: 11, project_id: 21 })), 'проект'); assert.equal(getForm4TypeLabel(form4Row('ROOT', { section_code: 'CAP' })), null); assert.equal(getForm4TypeLabel(form4Row('INPUT', { program_id: 11, project_id: 21 })), null); assert.equal(getForm4TypeLabel(form4Row('GROUP', { section_code: '1.' })), null); });