174 lines
8.7 KiB
JavaScript
174 lines
8.7 KiB
JavaScript
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);
|
||
});
|