2026-11-08 23:05:59 +04:00

174 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from 'node:assert/strict';
import test from 'node:test';
import {
buildRowHierarchy,
canAddRowForHeader,
getDataRowId,
getDeleteTarget,
getForm4TypeLabel,
getProjectAddTarget,
getRenameTarget,
isRenameResponse,
isStandaloneProject,
} 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);
});
const node = (row_type, header = {}, extra = {}) => ({ row_type, data: { header, ...extra } });
const names = (rows) => rows.map((r) => [r.data.header.name, names(r.subRows)]);
const rows = [
node('ROOT', { name: 'root' }),
node('GROUP', { name: 'prog1', program_id: 1 }),
node('ITEM', { name: 'in-prog', project_id: 10, program_id: 1 }),
node('INPUT', { name: 'e1', project_id: 10, program_id: 1 }, { line_id: 1 }),
node('ITEM', { name: 'solo', project_id: 11 }),
node('INPUT', { name: 'e2', project_id: 11 }, { line_id: 2 }),
node('GROUP', { name: 'prog2', program_id: 2 }),
node('ITEM', { name: 'solo2', project_id: 12, program_id: null }),
node('INPUT', { name: 'e3', project_id: 12 }, { line_id: 3 }),
];
test('I-1: standalone ITEM после программы — потомок ROOT, расходы — его дети', () => {
const tree = buildRowHierarchy(rows);
assert.deepEqual(names(tree), [
[
'root',
[
['prog1', [['in-prog', [['e1', []]]]]],
['solo', [['e2', []]]],
['prog2', []],
['solo2', [['e3', []]]],
],
],
]);
});
test('I-1: isStandaloneProject различает узлы', () => {
assert.equal(isStandaloneProject(rows[4]), true);
assert.equal(isStandaloneProject(rows[2]), false);
assert.equal(isStandaloneProject(rows[1]), false);
assert.equal(isStandaloneProject(rows[5]), false);
});